Skip to content

A Deep Dive into SvelteKit's Rendering Techniques

A Deep Dive into SvelteKit's Rendering Techniques

A Deep Dive into SvelteKit's Rendering Techniques

Introduction

SvelteKit is a meta-framework for Svelte that allows you to develop pages based on their content. At its core, SvelteKit introduces three fundamental strategies out of the box, each designed to streamline the development process and adapt to the specific needs of your project. These strategies enable you to easily create dynamic, responsive, and highly interactive web applications. These strategies, or as SvelteKit calls them, page options, are:

  • Prerender: Ideal for static content that doesn't change, making your pages lightning-fast to load.
  • SSR (Server-Side Rendering): Ideal for rendering full pages with dynamic content from a server.
  • CSR (Client-Side Rendering): Best for highly dynamic and interactive applications where content updates frequently based on user actions.

These page options can be applied to specific pages (when exported from **+page.js or +page.server.js) or to a group of pages (when exported from +layout.js or +layout.server.js). They can also be set across the entire application. You accomplish this by exporting it from the root layout. It's worth noting that child layouts and pages can supersede settings from parent layouts. This means you could activate prerendering for the whole app and then turn it off for certain pages that require dynamic server rendering (SSR).

In this blog post, we'll explore these page options and share some insights on how you might use them in everyday web projects.

Prerendering

Prerendering is akin to taking a snapshot of your web pages when you build your application; you might have heard of this as static rendering. This snapshot is then served to all users, ensuring lightning-fast load times since the server simply delivers the pre-built files without additional processing rather than dynamically generating the files for each request.

This method is perfect for content that remains unchanged across visits, such as blog posts, documentation, or landing pages. In other words, pages with no dynamic content.

There will be situations where you would like to avoid using this option, though. The rule of thumb is that if two different users will see different content, then this is a no-go. Other reasons are inconvenience for your needs. For example, your build times could drag if you end up prerendering tons of pages. Is that convenient to you? Well, that’s for you to decide!

The prerender option is turned off by default, so we need to enable it if we want to start using it. Export the following in the +page.server.js or just +page.js:

export const prerender = true; 

Likewise, if you have a group of pages, you could do the same inside a +layout.js or +layout.server.js . If your app is suitable to be all SSG (Static Side Generation), you could use [adapter-static](<https://github.com/sveltejs/kit/tree/main/packages/adapter-static>), which will output files suitable for use with any static web server. In cases where you happen to opt-in for this, you could turn off pages that need dynamic rendering:

export const prerender = false; 

Another cool feature about prerendering is that pages that fetch data from server routes can automatically inherit default values during the prerendering process. This feature simplifies data management and enhances the development workflow, especially when dealing with dynamic content that needs to be prerendered with specific data sets. Let's say you have a blog where each post fetches its content from a server route when the page loads. Normally, this would require the user's browser to make a request to the server at runtime. However, with prerendering, you can have this data fetched and embedded into the page at build time.

// +page.server.js
export const prerender = true;
export async function load({ fetch, params }) {
  const { slug } = params;
  const response = await fetch(`/api/posts/${slug}`);
  const post = await response.json();

  return { props: { post } };
}

// +page.svelte
<script>
  export let post;
</script>

<article>
  <h1>{post.title}</h1>
  <div>{post.content}</div>
</article>

// Server Route (src/routes/api/posts/[slug]/+server.js):

export async function get({ params }) {
  const { slug } = params;
  const post = findPostBySlug(slug); // logic to find a post by its slug

  if (post) {
    return {
      status: 200,
      body: post,
    };
  }

  return {
    status: 404,
  };
}

In this example, when the blog/[slug].sveltepage is prerendered, it makes a fetch call to /api/posts/[slug], which returns the post data. This data is then used to prerender the blog post page with the content already in place, allowing the page to load instantly for the user, with the blog post content visible even before any JavaScript is executed on the client side.

There’s a third and useful option when prerendering:

export const prerender = "auto"; 

Using this option is like telling SvelteKit to make a smart guess about whether to prerender your page in advance. You're saying, "Hey SvelteKit, you decide if this page should be prerendered based on what you find about the page"

SvelteKit analyzes your page and if it determines the page can be prerendered without complications, it will automatically generate a prerendered version. This process involves SvelteKit either crawling a link inside an already prerendered page, prerendering entry points or targeting pages that are specifically marked for prerendering set in entries.

An example is a blog section where you post articles regularly. The blog section has a main page that lists all your blog posts and individual pages for each blog post. The structure for this might be something like:

folder structure

In this setup, the /blog main entry page will be prerendered automatically. However, individual blog posts at paths like /blog/[slug] will not be prerendered unless linked directly from another prerendered page. For instance, if there’s a link to /blog/some-cool-slug, like: &lt;a href="/blog/some-cool-slug">, SvelteKit will be able to crawl that link and prerender that specific page as well.

Now let’s say that you would like to prerender your latest blog post, but there's no link for SvelteKit to crawl to this page. In these cases, you can explicitly add these pages to the prerender entries list. By doing so, SvelteKit will prerender every specified entry, ensuring that the content is immediately accessible to users.

You can configure the prerender entries directly in your svelte.config.js file or by exporting an entries function from a +page.js, a +page.server.js or a +server.js belonging to a dynamic route. Here’s how you can do it:

// /blog/[slug]/+page.server.js
export function entries() {
	return [
		{ slug: 'my-latest-post' }
	];
}

// OR:

// svelte.config.js

import adapter from "@sveltejs/adapter-auto";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
    prerender: {
      entries: ["/blog/my-latest-post"],
    },
  },
};

export default config;

SSR

SSR is similar to prerendering. It ensures that pages are first rendered on the server. This process generates the complete HTML content, which is then sent to the client for hydration. This approach enhances the initial page load performance and SEO, providing a better user experience.

Unlike prerendering, where pages are generated at build time, SSR pages are created at runtime. This key difference allows for the generation of dynamic content, making SSR particularly suited for applications that require personalized content for each user or real-time updates, such as user dashboards, e-commerce sites, and social media platforms.

Similar to prerendering, SSR comes with its own set of limitations, and there are scenarios where it might not be the best choice for your project:

  • SSR can be expensive. It can significantly load your server, especially for high-traffic sites, as each page request involves rendering content on the server. If server resources are constrained, this could lead to performance bottlenecks.
  • Highly Interactive Applications: Applications that rely heavily on user interactions and real-time updates (like games or interactive tools like an admin panel) might not benefit much from SSR. CSR can provide a smoother user experience in these cases, as it minimizes server requests after the initial load.
  • SEO Is Not YOUR Priority: If search engine optimization isn't a key concern for your project (for example, an internal dashboard or an app behind a login), the SEO benefits of SSR might not justify the additional complexity and server demands.

export const ssr = true is the option enabled by default. Thus, we can use its benefits from the start. To execute code exclusively on the server, such as fetching data from a database or an external API, SvelteKit offers a convention using +page.server.js files. This setup is ideal for server-side operations, ensuring sensitive logic and credentials are not exposed to the client.

// page.server.js
export async function load({ params }) {
  // Fetching Pokémon data from an external API
  const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${params.name}`);
  const pokemon = await res.json();

  // Returning the fetched data as props to the page
  if (res.ok) {
    return {
      props: { pokemon }, // This data is now available to our page component
    };
  } else {
    return {
      status: res.status,
      error: new Error('Failed to fetch the Pokémon'),
    };
  }
}

Note: When you employ +page.js in SvelteKit, the contained code is executed on both the server and client sides by default. However, if you intend for the code to run exclusively on the client side, you can disable SSR by setting export const ssr = false within your +page.js file. Doing so ensures that the code is only executed in the browser, adhering to client-side rendering principles and turning your app into a SPA. This is useful for cases where you don’t need the load on the server, or you don’t benefit from any of the benefits of SSR.

CSR

CSR plays a crucial role in making web pages interactive by hydrating them and incorporating JavaScript. JavaScript is crucial for adding interactivity to web pages—everything from animations and video players to form validations and dynamic content updates relies on JavaScript.

However, there are instances where JavaScript is unnecessary. Consider a prerendered 'About' page; enabling CSR on this page could be excessive, especially if it lacks interactive elements. In such scenarios, you can streamline your page by disabling CSR that comes enabled by default, which can be done by simply doing this:

// page.server.js
export const csr = false;

Using this trick smartly can make your web pages load fast because downloading JavaScript is sometimes heavy. The key lies in strategically combining this approach with other rendering techniques to optimize performance and user experience.

One factor to consider, though, is that turning off CSR also means you'll lose client-side routing. This requires you to depend on traditional browser navigation instead.

Conclusion

By thoughtfully applying these strategies, you can craft a SvelteKit application that performs exceptionally and delivers a fantastic user experience tailored to your audience's needs.

Wrapping up our journey through SSR with SvelteKit. We discovered how choosing the right way to render pages (like prerendering, SSR, or CSR) is super important and can make a big difference in how well a website works.

SvelteKit is awesome because it lets you pick the best method for your website. So, remember, playing with SvelteKit and these rendering methods can make your websites stand out. Use it to build websites that not only work great but are also fun and easy for people to use. Dive in, try things out, and see how your web projects can shine!