Performance & Deploy
Fast by default
Section titled “Fast by default”Because Svelte is a compiler (the very first lesson), the performance baseline is already good: there’s no virtual DOM runtime to ship, and components compile to targeted update code. You spend far less time fighting bundle size than in runtime-framework apps — the framework overhead riding along with your code is small.
That doesn’t make performance automatic. Your own code, images, third-party libraries, and data-loading choices still matter. But the starting point is lean, and the tools below help you keep it that way.
Measuring what you ship
Section titled “Measuring what you ship”Before optimizing, measure. vite build reports the output sizes, and Vite’s ecosystem has bundle visualizers (e.g. rollup-plugin-visualizer) to show what’s taking space — usually a heavy third-party dependency, not Svelte itself.
npm run build # vite build — prints chunk sizesWatch for: a large date/utility library imported whole, an icon set pulled in entirely, or a component eagerly loaded that could be lazy. SvelteKit code-splits per route automatically, so a heavy page doesn’t weigh down the rest of the app.
Adapters: choosing a deploy target
Section titled “Adapters: choosing a deploy target”A SvelteKit app is deployed through an adapter that transforms the build for a specific platform. You set it in svelte.config.js:
import adapter from '@sveltejs/adapter-auto';
export default { kit: { adapter: adapter(), },};| Adapter | Deploys to |
|---|---|
adapter-auto | Detects common platforms (Vercel, Netlify, Cloudflare…) automatically |
adapter-node | A long-running Node server you host yourself |
adapter-cloudflare | Cloudflare Pages / Workers (edge) |
adapter-static | A fully static site — HTML/CSS/JS on any CDN |
adapter-auto is the zero-config default; you switch to a specific adapter when you know your target (and need its options).
flowchart LR build["vite build (SvelteKit)"] --> adapter["adapter"] adapter --> node["Node server"] adapter --> edge["Cloudflare / edge"] adapter --> static["static CDN"]
Static vs server
Section titled “Static vs server”The big deployment decision mirrors the rendering choice:
- Static — with
adapter-static(orexport const prerender = trueon prerenderable routes), pages are built to HTML at build time and served from any CDN. Perfect for content that’s the same for everyone; no server needed. - Server — with a runtime adapter (node/cloudflare/…), pages render on demand for request-time data (the logged-in user, live data). You can mix: prerender the marketing pages, render the dashboard on demand.
Set export const prerender = true on the routes that can be static and let the rest render on demand — the same hybrid approach that keeps most of the site cacheable while a few routes stay dynamic.
svelte/reactivity: one more reason output stays small
Section titled “svelte/reactivity: one more reason output stays small”The “small by default” theme in this lesson isn’t only about the compiler — it extends to the standard library Svelte ships alongside it. svelte/reactivity (SvelteMap, SvelteSet, SvelteDate, and friends) gives you rune-native reactive collections without reaching for a writable store wrapper, which matters here because it’s one less dependency-shaped thing riding along in your bundle for a job the runtime already does natively. The state & stores module’s Built-in Stores lesson proves the reactivity difference against a plain Map in detail; this lesson’s takeaway is simpler: reaching for the rune-native version over a store-wrapped one is itself a small, free performance choice, not a trade-off.
Mental Model
Section titled “Mental Model”Everything in this lesson follows from one fact established in the very first lesson of this course: Svelte’s compiler already does the expensive analysis at build time, so “performance work” here is mostly about not undoing that advantage — not importing a heavy dependency whole, not choosing a server adapter for content that could be static (and therefore free to serve from a CDN with no compute at all), not reaching for a store-wrapped collection where a rune-native one does the same job with less around it. None of the tools in this lesson (bundle visualizers, adapters, svelte/reactivity) create speed on their own; they mostly just stop you from accidentally spending the budget the compiler handed you for free.
Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
adapter-auto | Zero-config, works out of the box on common platforms | Doesn’t expose platform-specific options (env handling, edge bindings) that a specific adapter does |
adapter-static / prerender = true | Free hosting on any CDN, no server compute cost per request (proven with real build inspection in the SvelteKit module) | Cannot serve request-time data — a route needing the logged-in user or live data cannot be prerendered |
adapter-node | Full control, runs anywhere Node runs (proven with a real build in the SvelteKit module) | You own the process — env vars like ORIGIN, proven in that lesson, instead of a platform managing it |
svelte/reactivity over a store wrapper | Rune-native, no extra dependency-shaped code for the same behavior | Only covers the collection types it ships (Map, Set, Date, URL, …) — a fully custom reactive shape still needs $state/a store |
Common Misconceptions
Section titled “Common Misconceptions”- “Prerendering (
adapter-static/prerender = true) is just a performance toggle you can flip on any route.” It’s a correctness constraint, not just a speed one — a prerendered route is rendered once at build time, so any route needing request-time data (cookies, the logged-in user, live data) cannot be prerendered at all, regardless of how much faster it would be. - “
$env/static/privateand$env/dynamic/privateare interchangeable — just pick whichever.” They resolve at different times entirely (build time vs request time) — proven with a real build’s inspected output in the SvelteKit module; swapping one for the other silently changes when a value can change without a rebuild. - “Using
svelte/reactivity’sSvelteMapinstead of a plainMapwrapped in a store is just a style preference.” It’s a real bundle and reactivity-model difference — no store subscription machinery, method-level reactivity instead of whole-object replacement — covered in full in the built-in-stores lesson linked above.
Reading AI-generated code
Section titled “Reading AI-generated code”When reviewing deployment and performance-sensitive code, check for:
- Is
export const prerender = trueset on a route that actually reads request-time data (cookies(), a session,url.searchParamsused for anything other than static generation) — a correctness bug, not just a missed optimization? - Does server code read a secret via
$env/dynamic/privatewhen$env/static/privatewould do (and be inlined, slightly faster) — or the reverse, reading$env/static/privatefor a value that’s genuinely meant to change per deployment without a rebuild? - Is a
Map/Setwrapped in a hand-rolledwritablestore wheresvelte/reactivity’sSvelteMap/SvelteSetwould give the same reactivity with less code and no store subscription overhead? - Is a specific adapter (
adapter-node,adapter-cloudflare) assumed to “just work” the same asadapter-auto— missing the platform-specific setup (env vars, bindings) the specific adapter actually needs, proven in the SvelteKit module foradapter-node’sORIGIN?
🐛 Spot the bug
export const prerender = true;
export async function load({ cookies }) {const session = cookies.get('session_id');return { user: await getUserBySession(session) };}Show the answer
prerender = true tells SvelteKit to render this route once, at build time, and serve the same static HTML to everyone forever after — but the load function reads cookies.get('session_id'), a value that’s different for every visitor and only exists at request time. There is no “request” during a build, so this isn’t a subtle performance trade-off; it’s a route that structurally cannot do what it’s trying to do. Depending on exactly what’s read, SvelteKit’s prerendering will either fail the build with an error, or (worse) silently bake one specific request’s data into the static output that every subsequent visitor then receives.
The fix is to remove prerender here entirely (or set it to false) — a per-user dashboard is exactly the “server” side of the static-vs-server split this lesson describes, not a candidate for the “static” side no matter how much faster static would be.