Skip to content

· 8 min read

Server-Side Rendering vs Static Generation in SvelteKit

SvelteKit lets you mix SSR, SSG, and client-side rendering on a per-route basis. Here's when to use each approach and how to combine them effectively.

By Tanner Goins

Earth at night from orbit, city lights glowing across the dark surface under a starry sky

One of SvelteKit’s best features is one that most tutorials gloss over: you don’t have to pick one rendering strategy for your entire site. You can render your marketing pages statically, your dashboard with server-side rendering, and your interactive widgets client-side. All in the same project, with the same components.

After building and rebuilding this site and living with these patterns for over a year in Svelte 5, I’ve developed strong opinions about when each approach makes sense. Here’s the practical guide.


The Three Rendering Strategies

Before diving into when to use what, let’s be precise about what each strategy does.

Static Generation (SSG / Prerendering)

The page is built once at deploy time. The HTML file is generated, saved, and served directly from a CDN. No server runs at request time.

In SvelteKit, you enable this per route:

// +page.ts
export const prerender = true;

Result: A static HTML file. Lightning fast. No server needed.

Server-Side Rendering (SSR)

The page is rendered on the server for every request. The server runs your load function, generates HTML, and sends it to the client.

This is SvelteKit’s default behavior. If you don’t set prerender = true, your page is server-rendered.

// +page.server.ts
export async function load({ params }) {
  const post = await db.getPost(params.slug);
  return { post };
}

Result: Fresh data on every request. Requires a running server.

Client-Side Rendering (CSR)

The page is rendered entirely in the browser. The server sends a minimal HTML shell, and JavaScript takes over to fetch data and render the UI.

// +page.ts
export const ssr = false;

Result: No server rendering at all. Data fetched client-side. Not SEO-friendly.


When to Use Static Generation

Use SSG when the content doesn’t change between deployments.

Static generation is the fastest and cheapest option. The HTML is pre-built, cached on a CDN edge node, and served in milliseconds. There’s no server to maintain, no cold starts, no database queries at request time.

Perfect For

Blog posts and documentation. This site’s blog posts are prerendered. The content is in markdown files that change only when I deploy. There’s no reason to render them on every request when the result would be identical every time.

// src/routes/blog/[post]/+page.ts
export const prerender = true;

Marketing pages. Homepage, about page, pricing page, services page. These change infrequently and benefit enormously from edge caching. For a small business website, prerendering the entire site is often the right call.

Sitemaps and RSS feeds. Generated once at build time from your content:

// src/routes/sitemap.xml/+server.ts
export const prerender = true;

Legal pages. Privacy policy, terms of service. Static content that rarely changes.

The Tradeoffs

  • Content is only as fresh as your last deploy. If you update a blog post, the change won’t be visible until you redeploy.
  • Dynamic routes need to be enumerable. SvelteKit needs to know every URL to prerender at build time. For blog posts, this means you need an entries() function that returns all slugs.
  • No per-request personalization. Every visitor sees the same page. If you need “Welcome back, Sarah” in the header, SSG alone can’t do it.

When to Use Server-Side Rendering

Use SSR when the content depends on the request: the user, the time, the query parameters, or data that changes between requests.

Perfect For

Dashboards and authenticated pages. The content varies per user. You need to check their session, query their data, and render a personalized page. SSR handles this naturally:

// src/routes/dashboard/+page.server.ts
export async function load({ locals }) {
  const user = locals.user;
  const orders = await db.getOrders(user.id);
  return { user, orders };
}

Search results. The content depends on the query. You can’t prerender every possible search. You need to run the query at request time.

Real-time data. Stock prices, weather, live scores. The data changes constantly, so a prerendered page would be immediately stale.

Pages with access control. If some pages should only be visible to authenticated users, SSR lets you check authentication server-side and redirect if needed:

// src/routes/admin/+page.server.ts
export async function load({ locals }) {
  if (!locals.user?.isAdmin) {
    throw redirect(303, "/login");
  }
  return { users: await db.getAllUsers() };
}

The Tradeoffs

  • Requires a running server. You can’t deploy to a static host. You need a Node.js server, a serverless function, or an edge runtime.
  • Slower than static. Every request runs server-side code. Even with caching, this is slower than serving a pre-built HTML file.
  • Cold starts. On serverless platforms, the first request after an idle period can be slow. Subsequent requests are faster, but the initial latency is noticeable.

SSR + Caching: The Best of Both Worlds

For pages that change occasionally but not on every request, combine SSR with HTTP caching:

// +page.server.ts
export async function load({ setHeaders }) {
  setHeaders({
    "cache-control": "public, max-age=300", // Cache for 5 minutes
  });

  const products = await db.getProducts();
  return { products };
}

This gives you fresh data (within 5 minutes) with the performance of serving cached responses. It’s my go-to pattern for product listings, category pages, and anything that changes daily rather than per-request.


When to Use Client-Side Rendering

Use CSR sparingly, for highly interactive components that don’t need SEO and rely on client-side state.

Perfect For

Interactive tools and calculators. A mortgage calculator, a color picker, a configuration builder. These are user-driven interactions that don’t need to be indexed by search engines.

Authenticated app shells. If your entire application is behind a login, search engines can’t access it anyway. CSR can work for the app interior while SSR handles the login page.

The Tradeoffs

  • Not SEO-friendly. Search engines may not execute JavaScript reliably. If the page needs to rank in search results, don’t use CSR.
  • Slower initial load. The browser downloads JavaScript, executes it, fetches data, then renders. Users see a loading spinner instead of content.
  • Accessibility concerns. Screen readers and assistive technologies work better with server-rendered HTML than with JavaScript-generated DOM.

My recommendation: avoid CSR as a page-level strategy. Use it for individual components within an SSR or SSG page when those components need client-side interactivity.


Mixing Strategies in SvelteKit

This is where SvelteKit shines. You configure rendering per route, not per project.

This Site’s Strategy

Here’s roughly how this site is set up:

RouteStrategyWhy
/ (homepage)SSRDynamic content sections
/blogSSRBlog listing with categories
/blog/[post]PrerenderedContent is static markdown
/aboutPrerenderedRarely changes
/contactSSRForm handling server-side
/resumePrerenderedRarely changes
/sitemap.xmlPrerenderedGenerated at build time

Layout-Level Configuration

SvelteKit lets you set rendering defaults at the layout level, so all child routes inherit the setting:

// src/routes/blog/+layout.ts
export const prerender = true;

Now every route under /blog/ is prerendered by default. If a specific route needs SSR, it can override:

// src/routes/blog/search/+page.ts
export const prerender = false; // Override: this page needs SSR

The entries() Function

For prerendered dynamic routes, SvelteKit needs to know which URLs exist at build time:

// src/routes/blog/[post]/+page.ts
export const prerender = true;

export function entries() {
  return [
    { post: "my-first-post" },
    { post: "another-post" },
    // ... all post slugs
  ];
}

In practice, you’d generate this list from your content source (reading markdown files, querying a CMS, etc.).


How Rendering Strategy Affects SEO

The rendering strategy you choose has direct implications for how search engines see your pages. This matters for your SEO strategy.

Prerendered Pages

Search engines love these. The HTML is complete, fast to load, and easy to crawl. This is the best option for any page that needs to rank.

Server-Rendered Pages

Also excellent for SEO. The HTML is complete when the crawler receives it. The main risk is if your server is slow. Slow pages get lower crawl priority and worse Core Web Vitals scores.

Client-Rendered Pages

Risky for SEO. Google claims to render JavaScript, but the rendering budget is limited. Important pages should never rely on client-side rendering for their primary content.

The Practical Rule

If the page needs to appear in search results, prerender it or server-render it. If it doesn’t need SEO (authenticated dashboards, internal tools), CSR is fine.


Performance Comparison

Here’s how the strategies compare on the metrics that matter:

MetricSSGSSRCSR
Time to First Byte (TTFB)Excellent (CDN edge)Good (server response)Fast (empty shell)
First Contentful Paint (FCP)ExcellentGoodPoor (waits for JS)
Largest Contentful Paint (LCP)ExcellentGoodPoor
Cumulative Layout Shift (CLS)Excellent (stable)GoodRisky (content shifts)
Hosting costLowest (static files)Medium (server)Lowest (static files)
Data freshnessDeploy-time onlyReal-timeReal-time

For most small business and content sites, prerendering wins across the board. The hosting cost alone (static files on a CDN vs. a running server) can be the deciding factor. This is one of the reasons I advocate for choosing technology that fits the need, not the trend.


Start Simple

If you’re building a new SvelteKit project and aren’t sure which strategy to use, start here:

  1. Prerender everything you can. Blog posts, marketing pages, docs: if it doesn’t change per request, prerender it.
  2. Use SSR for authenticated or dynamic pages. Dashboards, search, user-specific content.
  3. Avoid CSR for full pages. Use it for individual interactive components, not as a page-level strategy.
  4. Add caching to SSR pages that don’t need real-time freshness.

SvelteKit makes this easy because you can change the strategy for any route by adding a single line of configuration. Start with the simplest approach and adjust as your needs become clearer.

The rendering strategy isn’t something to agonize over. Pick the right default, override where needed, and move on to building features.