Skip to content

Performance & Deploy

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.

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.

Terminal window
npm run build # vite build — prints chunk sizes

Watch 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.

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(),
},
};
AdapterDeploys to
adapter-autoDetects common platforms (Vercel, Netlify, Cloudflare…) automatically
adapter-nodeA long-running Node server you host yourself
adapter-cloudflareCloudflare Pages / Workers (edge)
adapter-staticA 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"]
One build, many deploy targets via adapters

The big deployment decision mirrors the rendering choice:

  • Static — with adapter-static (or export const prerender = true on 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.

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.

What you getBenefitCost
adapter-autoZero-config, works out of the box on common platformsDoesn’t expose platform-specific options (env handling, edge bindings) that a specific adapter does
adapter-static / prerender = trueFree 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-nodeFull 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 wrapperRune-native, no extra dependency-shaped code for the same behaviorOnly covers the collection types it ships (Map, Set, Date, URL, …) — a fully custom reactive shape still needs $state/a store
  • “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/private and $env/dynamic/private are 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’s SvelteMap instead of a plain Map wrapped 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.

When reviewing deployment and performance-sensitive code, check for:

  • Is export const prerender = true set on a route that actually reads request-time data (cookies(), a session, url.searchParams used for anything other than static generation) — a correctness bug, not just a missed optimization?
  • Does server code read a secret via $env/dynamic/private when $env/static/private would do (and be inlined, slightly faster) — or the reverse, reading $env/static/private for a value that’s genuinely meant to change per deployment without a rebuild?
  • Is a Map/Set wrapped in a hand-rolled writable store where svelte/reactivity’s SvelteMap/SvelteSet would 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 as adapter-auto — missing the platform-specific setup (env vars, bindings) the specific adapter actually needs, proven in the SvelteKit module for adapter-node’s ORIGIN?

🐛 Spot the bug

src/routes/dashboard/+page.server.ts
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.

Why is a Svelte app small by default?
When you measure a large bundle, what is usually to blame?
What is a SvelteKit adapter for?
What is wrong here?
// src/routes/dashboard/+page.server.ts
export const prerender = true;

export async function load({ cookies }) {
  return { user: await getUserBySession(cookies.get('session_id')) };
}
How do you make some routes static and others render on demand?
What does svelte/reactivity's SvelteMap give you over a plain Map wrapped in a writable store?
import { SvelteMap } from 'svelte/reactivity';
let cache = new SvelteMap();