SSR vs CSR vs SSG
The web has three fundamental approaches to rendering pages: the server can do it, the client can do it, or you can do it ahead of time at build. SvelteKit is one of the few frameworks that supports all three strategies per-route, letting you pick the right rendering mode for each page in your application rather than committing to one approach globally.
Server-Side Rendering
SSR is the default in SvelteKit. When a user requests a page, the server runs your Svelte component, produces HTML, and sends it to the browser. The browser displays the HTML immediately, then SvelteKit's JavaScript kicks in to "hydrate" the page, attaching event listeners and making it interactive.
// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
const user = locals.user;
const stats = await fetchDashboardStats(user.id);
return {
user: {
name: user.name,
email: user.email
},
stats
};
};
<!-- src/routes/dashboard/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<h1>Welcome back, {data.user.name}</h1>
<div class="stats-grid">
<div class="stat">
<span class="label">Revenue</span>
<span class="value">${data.stats.revenue.toLocaleString()}</span>
</div>
<div class="stat">
<span class="label">Active Users</span>
<span class="value">{data.stats.activeUsers.toLocaleString()}</span>
</div>
</div>
The user sees the dashboard content before any JavaScript loads. Search engines see fully rendered HTML. Once hydration completes, the page becomes a full single-page application.
Client-Side Rendering
CSR means the server sends a minimal HTML shell and JavaScript does all the rendering in the browser. No HTML is generated on the server for the page content. This is how traditional SPAs like Create React App work.
In SvelteKit, you enable CSR-only by disabling SSR:
// src/routes/admin/+page.ts
export const ssr = false;
With ssr = false, the server sends a blank page with a script tag. The browser downloads JavaScript, runs your component, and renders the content. The user sees nothing until JavaScript loads and executes.
CSR makes sense when the page is behind authentication and SEO does not matter, when the page relies heavily on browser APIs like window or localStorage, or when you are building a tool that only internal users access.
// src/routes/admin/+layout.ts
// Disable SSR for all admin routes
export const ssr = false;
<!-- src/routes/admin/editor/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
let editor: HTMLDivElement;
let content = $state('');
onMount(() => {
// Safe to use browser APIs here since this only runs client-side
const saved = localStorage.getItem('draft');
if (saved) {
content = saved;
}
// Initialize a rich text editor that needs the DOM
initializeEditor(editor);
});
</script>
<div bind:this={editor}></div>
Static Site Generation
SSG renders pages at build time. The HTML is generated once during vite build and served as static files. No server is needed at runtime, just a CDN or static file host.
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';
export const prerender = true;
export const load: PageLoad = async ({ params, fetch }) => {
const response = await fetch(`/api/posts/${params.slug}`);
const post = await response.json();
return { post };
};
At build time, SvelteKit's prerender crawler discovers all blog post URLs, runs the load function for each, renders the HTML, and writes static files. The result is a directory of HTML files that any static host can serve.
SSG is ideal for marketing pages, documentation sites, blogs, and any content that does not change between deployments.
The Page Options
SvelteKit provides three page options that control rendering behavior. You export them from +page.ts or +layout.ts:
// src/routes/+page.ts
export const ssr = true; // Default: server renders HTML
export const csr = true; // Default: client hydrates and takes over
export const prerender = false; // Default: render on each request, not at build
These options combine in meaningful ways:
// Full SSR with hydration (default)
export const ssr = true;
export const csr = true;
// SSR without hydration — static HTML, no JavaScript on the page
export const ssr = true;
export const csr = false;
// CSR only — blank page, client renders everything
export const ssr = false;
export const csr = true;
// Prerendered — built at build time, hydrated on load
export const prerender = true;
export const ssr = true;
export const csr = true;
Setting csr = false is powerful for pages that need zero JavaScript. Marketing landing pages, legal pages, and content-only pages can ship no JS at all.
Choosing the Right Mode
The decision depends on three questions: does the page need SEO, does the content change per user, and how dynamic is the interaction?
// Marketing pages: SSG — fast, cacheable, no server cost
// src/routes/pricing/+page.ts
export const prerender = true;
// Blog posts: SSG — content changes only on deploy
// src/routes/blog/[slug]/+page.ts
export const prerender = true;
// Product pages: SSR — SEO matters, content changes frequently
// src/routes/products/[id]/+page.server.ts
export const load: PageServerLoad = async ({ params }) => {
const product = await db.products.findUnique({ where: { id: params.id } });
return { product };
};
// Dashboard: CSR — no SEO needed, highly interactive
// src/routes/app/+layout.ts
export const ssr = false;
Per-Route Configuration in Practice
A real application mixes rendering modes across routes. Layout-level settings cascade to all child routes:
// src/routes/+layout.ts
// Default for the whole app: SSR with hydration
export const ssr = true;
export const csr = true;
// src/routes/marketing/+layout.ts
// Marketing section: prerendered, no JS needed
export const prerender = true;
export const csr = false;
// src/routes/app/+layout.ts
// App section: CSR only, behind auth
export const ssr = false;
// src/routes/api/health/+server.ts
// API routes are always server-side, no rendering involved
import { json } from '@sveltejs/kit';
export function GET() {
return json({ status: 'ok' });
}
Hydration
Hydration is the process of making server-rendered HTML interactive. The server sends HTML, the browser paints it, then SvelteKit's JavaScript loads and "attaches" event handlers to the existing DOM rather than re-rendering it.
<!-- This HTML is visible before JS loads -->
<button onclick={handleClick}>
Count: {count}
</button>
<!-- After hydration, the onclick handler is active -->
<!-- The user can now click the button and see the count change -->
Hydration has a cost. The browser must download JavaScript, parse it, and walk the DOM to attach handlers. For content-heavy pages where interactivity is minimal, you can skip hydration entirely with csr = false.
Common Pitfalls
- Assuming SSR means no client JavaScript. SSR generates HTML on the server, but by default SvelteKit still sends JavaScript for hydration. The page is interactive after hydration completes. To skip JS entirely, set
csr = false. - Using browser APIs in SSR code. Code that runs during SSR cannot access
window,document, orlocalStorage. Wrap browser-only code inonMountor checktypeof window !== 'undefined'. - Setting
ssr = falseglobally. This kills SEO for your entire site. Only disable SSR on routes where you genuinely do not need it. - Forgetting that
prerenderhappens at build time. Prerendered pages cannot access request-specific data like cookies or headers. If your page needs user-specific content, SSR is the right choice. - Not understanding the hydration cost. Large JavaScript bundles slow down hydration. A page might appear fast (server HTML) but feel sluggish (waiting for hydration). Monitor Time to Interactive, not just First Contentful Paint.
Key Takeaways
- SvelteKit supports SSR, CSR, and SSG per-route through the
ssr,csr, andprerenderpage options. - SSR is the default and best for dynamic content that needs SEO. The server renders HTML, the client hydrates it.
- CSR is for pages where SEO does not matter and browser APIs are essential. Set
ssr = false. - SSG pre-renders at build time. Perfect for static content, blogs, and marketing pages. Set
prerender = true. - Setting
csr = falseproduces zero-JavaScript pages, ideal for content-only routes. - Layout-level settings cascade to child routes, letting you configure entire sections at once.
- Always guard browser API access with
onMountortypeof windowchecks when SSR is enabled.