SvelteKit Config
When to use this skill
Use this skill whenever you are working on SvelteKit's configuration, build pipeline, deployment, performance, images, accessibility, SEO, debugging, or migration concerns. This includes:
- Choosing or configuring an adapter (
adapter-node,adapter-static,adapter-cloudflare,adapter-netlify,adapter-vercel) - Building (
vite build) and previewing (vite preview) production apps - Advanced routing (rest params, optional params, matchers, route sorting, filename encoding)
- Advanced layouts (route groups, breaking out of layouts with
@) - Auth integration patterns (sessions vs tokens, hooks-based auth)
- Performance optimization (code splitting, asset opt, avoiding waterfalls)
- Image handling (
@sveltejs/enhanced-img, CDN loading,<picture>/<img>patterns) - Accessibility (route announcements, focus management,
langattribute) - SEO (titles, meta tags, sitemaps, JSON-LD)
- Breakpoint debugging (VS Code, Chrome DevTools)
- Migrating from SvelteKit v1 or Sapper
For routing/data/load basics, see sveltekit-overview. For Svelte components/runes, see svelte-runes.
Critical: Building and previewing
vite build runs in two stages: Vite produces an optimized production build, then your adapter tailors the output for the target platform. Prerendering executes during build.
During the build, SvelteKit loads your +page/layout(.server).js for analysis. Code that must NOT run at build time should guard with building from $app/environment:
import { building } from '$app/environment';
import { initialiseDatabase } from '$lib/server/database';
if (!building) initialiseDatabase();
export function load() { /* ... */ }
After building, run vite preview (or npm run preview) to test the production build locally. Preview runs in Node, so adapter-specific behavior (e.g. Cloudflare's platform object) does NOT apply — use wrangler dev for Cloudflare or the platform's CLI for accurate testing.
npm run build # vite build + adapter
npm run preview # vite preview (Node)
Critical: Adapters — when to use which
The adapter is configured in svelte.config.js under kit.adapter. adapter-auto ships by default in new projects and picks the right adapter for known deployment environments (Cloudflare Pages, Netlify, Vercel, Azure SWA, SST, Google Cloud Run). Once you've chosen a target, install that adapter explicitly so it lands in your lockfile.
| Target | Adapter | Notes |
|---|---|---|
| Node server / Docker / VM | @sveltejs/adapter-node |
Standalone Node server. Most flexible. |
| Static hosting (no SSR) | @sveltejs/adapter-static |
SSG or SPA fallback. |
| Cloudflare Workers / Pages | @sveltejs/adapter-cloudflare |
Unified adapter for both. |
| Netlify | @sveltejs/adapter-netlify |
Node functions or Deno edge. |
| Vercel | @sveltejs/adapter-vercel |
Serverless or edge, ISR support. |
Adapter quick guide:
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default { kit: { adapter: adapter() } };
Platform-specific context
Some adapters expose platform info (KV namespaces, Durable Objects, env vars) via event.platform in hooks/server routes. Type augmentation in src/app.d.ts:
declare global {
namespace App {
interface Platform {
env: { MY_KV: KVNamespace };
}
}
}
export {};
Always prefer $env/static/private for environment variables — $env/dynamic/* cannot be used during prerendering.
Critical: Advanced routing
SvelteKit routes are filesystem-based. Beyond basic dynamic segments, you have several advanced features.
Rest parameters — [...rest]
Match an unknown number of segments. src/routes/a/[...rest]/z/+page.svelte matches /a/z, /a/b/z, /a/b/c/z. The rest param is a string with /-separated segments.
src/routes/[org]/[repo]/tree/[branch]/[...file]/+page.svelte
Use rest parameters to render custom 404s — add [...path]/+page.js that calls error(404) so a nested +error.svelte is reached.
Optional parameters — [[lang]]
Wrap with double brackets to make a param optional. [[lang]]/home matches both /home and /en/home. An optional param cannot follow a rest param.
Matching — [name=type]
Constrain a parameter with a matcher from src/params/:
// src/params/fruit.js
/** @type {import('@sveltejs/kit').ParamMatcher} */
export function match(param) {
return param === 'apple' || param === 'orange';
}
Then write src/routes/fruits/[page=fruit]/+page.svelte. Matchers run on both server and browser.
Sorting
When multiple routes match, SvelteKit sorts by:
- More specific routes win (fewer params = more specific)
- Matchers (
[name=type]) beat unconstrained ([name]) [[optional]]and[...rest]are lowest priority unless they're the final segment- Ties resolved alphabetically
Encoding special characters
Use hex escape [x+nn] in folder names: / → [x+2f], : → [x+3a], etc. Use [u+nnnn] for Unicode (no surrogate pairs needed).
src/routes/smileys/[x+3a]-[x+29]/+page.svelte
# matches /smileys/:-)
Critical: Advanced layouts
By default, the layout hierarchy mirrors the folder hierarchy. Use these patterns to reshape it.
Route groups — (group)
Parentheses-wrapped folder names don't appear in the URL. Use to share a layout between routes without affecting URL structure.
src/routes/
├ (app)/dashboard/+page.svelte
├ (app)/+layout.svelte # app shell
├ (marketing)/about/+page.svelte
├ (marketing)/+layout.svelte # marketing shell
└ +layout.svelte
Breaking out — +page@layout.svelte
Append @<segment> (or @ for root) to reset the layout chain. +page@(app).svelte inherits only from (app)/+layout.svelte.
Options: +page@[id].svelte, +page@item.svelte, +page@(app).svelte, +page@.svelte.
Layouts can also break out: +layout@.svelte rewinds to root for everything below it.
Reset to root
If you want most of your app under one layout but a few routes to escape, put everything inside a group except the outliers:
src/routes/
├ (app)/...
└ admin/+page.svelte # does NOT inherit (app) layout
Critical: Auth integration
Auth = authentication (who is this?) + authorization (what can they do?).
Sessions vs tokens
- Sessions: ID stored in DB. Revocable instantly, requires DB lookup per request.
- JWT tokens: Self-contained, no DB lookup, but cannot be revoked immediately. Better latency.
Integration pattern
Check auth cookies in src/hooks.server.js, populate event.locals.user, then read locals in +page.server.js / +server.js load functions.
// src/hooks.server.js
export async function handle({ event, resolve }) {
event.locals.user = await getUser(event.cookies.get('session'));
return resolve(event);
}
Libraries
npx sv add better-auth— Better Auth integration via Svelte CLI- Lucia auth guide — reference SvelteKit examples for session-based auth
Always require path: '/' when calling cookies.set(...) in SvelteKit v2.
Critical: Performance optimization
SvelteKit ships with: code-splitting, asset preloading, file hashing, request coalescing, parallel loading, data inlining, conservative invalidation, link preloading. To go further:
Diagnose
- PageSpeed Insights / Lighthouse / WebPageTest
- Chrome DevTools Network + Performance tabs
- Test in preview mode (after
vite build), not dev mode
Assets
- Use
@sveltejs/enhanced-imgfor images (smaller formats, intrinsic dimensions) - Lazy-load below-the-fold videos with
preload="none" - Subset fonts; preload critical fonts via
handlehook'spreloadfilter
Code size
- Use Svelte 5 (smaller than 4)
- Use
rollup-plugin-visualizerto find heavy packages - Prefer dynamic
import()for conditional code - Push third-party scripts to web workers (Partytown)
Avoid waterfalls
- Use server
loadfunctions for backend calls (avoid client → server → backend chains) - Issue parallel queries with
Promise.all/ DB joins - SPA mode causes waterfalls — prerender instead
Hosting
- Deploy frontend near backend (or use edge)
- Ensure HTTP/2+
Critical: Images
Vite's built-in handling
<script>
import logo from '$lib/assets/logo.png';
</script>
<img alt="logo" src={logo} />
Vite hashes the filename and inlines small assets.
@sveltejs/enhanced-img
Build-time image optimization: generates avif/webp, sets intrinsic width/height (prevents CLS), strips EXIF.
// vite.config.js — plugin order matters
import { enhancedImages } from '@sveltejs/enhanced-img';
import { sveltekit } from '@sveltejs/kit/vite';
export default { plugins: [enhancedImages(), sveltekit()] };
Usage:
<enhanced:img src="./image.jpg" alt="..." sizes="min(1280px, 100vw)" />
Generated <picture> includes multiple formats and sizes for HiDPI. Provide 2x source for retina displays.
Custom widths: <enhanced:img src="./image.png?w=1280;640;400" />
Per-image transforms: <enhanced:img src="./image.jpg?blur=15" />
Dynamic CDN loading
For images unavailable at build time (CMS, DB), use a CDN library:
@unpic/svelte— CDN-agnosticsvelte-cloudinary— Cloudinary- CMS-bundled: Contentful, Storyblok, Contentstack
Best practices
- Set
fetchpriority="high"and avoidloading="lazy"for LCP images - Always provide
alttext - Don't use
em/reminsizes - Mix strategies: Vite for
<meta>, enhanced-img for hero, CDN for user content
Critical: Accessibility
SvelteKit provides an accessible foundation; you're still responsible for app-level a11y.
Route announcements
SvelteKit injects a live region that reads the <title> after each client-side navigation. Every page must have a unique, descriptive <title> in a <svelte:head>:
<svelte:head>
<title>Todo List</title>
</svelte:head>
Focus management
After each navigation, SvelteKit focuses <body> (or [autofocus] element if present). Override with afterNavigate for custom behavior:
import { afterNavigate } from '$app/navigation';
afterNavigate(() => document.querySelector('.focus-me')?.focus());
Use data-sveltekit-keepfocus on a <form> to preserve input focus. goto(url, { keepFocus: true }) for programmatic nav.
lang attribute
Set <html lang="en"> (or your language) in src/app.html. For multi-language sites, use a transformPageChunk in handle to set per-request.
Critical: SEO
SvelteKit ships with SSR, normalized trailing-slash URLs, and good defaults. Manual steps:
Per-page meta
<svelte:head>
<title>Page Title — Site Name</title>
<meta name="description" content="..." />
<meta property="og:title" content="..." />
<meta property="og:description" content="..." />
<meta property="og:image" content="..." />
<meta property="og:type" content="website" />
<link rel="canonical" href="https://..." />
</svelte:head>
Common pattern: return SEO data from load, render in root layout's <svelte:head>.
Sitemap
// src/routes/sitemap.xml/+server.js
export async function GET() {
return new Response(`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<!-- <url> entries -->
</urlset>`, { headers: { 'Content-Type': 'application/xml' } });
}
JSON-LD structured data
Inject via <svelte:head> with <script type="application/ld+json">.
Critical: Migration
SvelteKit v1 → v2
Use npx sv migrate sveltekit-2 (or npx svelte-migrate for older projects). Breaking changes:
error(...)andredirect(...)are no longer thrown — just call themcookies.set/delete/serializerequirespath: '/'- Top-level promises in
loadare NOT awaited automatically — useawaitexplicitly gotono longer accepts external URLs (usewindow.location.href)pathsare now consistently relative (defaulttrue)$app/storesdeprecated in 2.12 — migrate to$app/state(Svelte 5 runes)- Svelte 4 required, Node 18.13+
vitePreprocessmust be imported from@sveltejs/vite-plugin-svelte
Sapper → SvelteKit
package.json: add"type": "module", removepolka/sapper/sirv/compression- Add
@sveltejs/kit+ an adapter - Scripts:
sapper build→vite build,sapper dev→vite dev,node __sapper__/build→node build src/template.html→src/app.html(replace%sapper.*placeholders)- Routes:
routes/about/index.svelte→routes/about/+page.svelte _layout.svelte→+layout.svelte,_error.svelte→+error.sveltepreload→load(different API: singleeventarg, nothis.fetch)stores/navigatingfrom$app/stores(or$app/statein 2.12+)- Regex routes → matchers in
src/params/ sapper:prefetch→data-sveltekit-preload-datasapper:noscroll→data-sveltekit-noscroll- Move internal libs from
src/node_modules/...tosrc/lib
Critical: Debugging
VS Code
Built-in debug terminal works out of the box:
CMD/Ctrl+Shift+P→ "Debug: JavaScript Debug Terminal"- Run
npm run devin that terminal - Set breakpoints, hit them in the browser
Or create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{ "command": "npm run dev", "name": "dev", "request": "launch", "type": "node-terminal" }
]
}
Chrome DevTools / Edge
NODE_OPTIONS="--inspect" npm run dev
Open the site, then click the "Open dedicated DevTools for Node.js" icon (Node logo, top-left). Or visit chrome://inspect.
Quick Fixes
buildingflag: Wrap one-time init code inif (!building)to prevent it running duringvite build.fallbackinadapter-static: Use200.htmlfor SPA mode, but avoidindex.html(conflicts with prerendered/).- GitHub Pages: Set
paths.baseto repo name andfallback: '404.html'. Add.nojekylltostatic/. - Node origin: Set
ORIGINenv var if you're behind a proxy and see "Cross-site POST form submissions are forbidden". - XFF depth: Behind N proxies, set
XFF_DEPTH=NsogetClientAddress()returns the real client IP. - Compression: Use
@polka/compressionnotcompression— SvelteKit streams responses. .envin production: Production doesn't auto-load.env. Usenode --env-file=.env build(Node 20.6+) or-r dotenv/config.platformonly in dev/preview: Test Cloudflare/Netlify/Vercel platform APIs with their respective CLIs (wrangler dev,netlify dev,vercel dev).fsnot available: Usereadfrom$app/server(works in edge by fetching from deployed assets).gotoexternal URL: Usewindow.location.hrefinstead.- Cookie path: SvelteKit v2 requires
path: '/'oncookies.set(...). - Trailing slash: Set
trailingSlash: 'always'if your host doesn't serve/a.htmlfrom/a.
Gotchas
- Preview is not production:
vite previewruns in Node, doesn't emulate adapter-specific behavior. Use the platform's CLI for accurate local testing. adapter-autois just for zero-config: Once you've decided on a target, install the real adapter so it lands in the lockfile and you can pass options.- Server bundle size on Cloudflare: Workers have a size limit. Move large libraries to client-only imports if you hit it.
- AWS / Node 18: SvelteKit v2 requires Node 18.13+. Older Node versions fail with cryptic errors.
- ISR + prerender: ISR has no effect on routes with
export const prerender = true. adapter-cloudflare-workersdeprecated: Use@sveltejs/adapter-cloudflare(withassets.directory+assets.bindingin wrangler config).- Svelte 4 → 5:
$app/storesis deprecated; use$app/state. Update Svelte first, then SvelteKit. - Tailwind +
<enhanced:img>: Tag-name selectors needenhanced\:imgto escape the colon. - Match
data-sveltekit-noscrollfor chat/SPA-like UIs: Default scroll-to-top behavior can break infinite-scroll apps. - Vite asset inlining: Assets below
assetsInlineLimit(default ~4kb) get base64-inlined. Excludessvgfor enhanced-img.
FAQ
Should I use adapter-auto?
Yes for prototyping. Once you've chosen a target, swap to the specific adapter so you can configure it.
SPA mode vs full SSR?
Full SSR with adapter-static (prerender) is best for SEO/perf. SPA fallback is for when you must deploy to static-only hosting without prerendering everything. SPA hurts SEO and performance.
When should I prerender?
Pages that return the same content for every visitor (marketing pages, blog posts, docs). Add export const prerender = true to the route.
How do I know which adapter is being used in production?
adapter-auto logs at build time which one it picked. Otherwise, check svelte.config.js.
Can I use the same svelte.config.js for multiple adapters?
No — you must run vite build once per adapter. Common CI pattern: build matrix per target.
Why is my Cloudflare Worker huge?
Bundle bloat — server-side imports pull in large deps. Move them to dynamic import() or client-only.
Do I need Svelte 5 for SvelteKit 2?
You need Svelte 4+. Svelte 5 is recommended for $app/state and runes.
Examples & References
Examples
| File | What it covers |
|---|---|
examples/build-preview.md |
vite build, vite preview, env vars at build time |
examples/adapter-node.md |
dev/build/deploy, custom server, env vars |
examples/adapter-static.md |
prerender all, SPA fallback, GitHub Pages |
examples/adapter-cloudflare.md |
workers, pages, runtime APIs, env vars |
examples/adapter-netlify.md |
deploy, edge functions, env vars |
examples/adapter-vercel.md |
deploy, image opt, ISR, env vars, skew protection |
examples/writing-adapter.md |
custom adapter using the builder API |
examples/advanced-routing.md |
rest params, optional, matchers, sort, encoding |
examples/advanced-layouts.md |
nested layouts, named layouts, error reset, groups |
examples/performance.md |
code splitting, asset opt, hydration opt |
examples/images.md |
enhanced-img, dynamic CDN loading, best practices |
examples/accessibility.md |
route announcements, focus management, lang attribute |
examples/seo.md |
meta tags, OG tags, JSON-LD, sitemaps |
examples/debugging.md |
VS Code launch.json, browser breakpoints |
examples/migration-v2.md |
error/redirect changes, cookie path, top-level promises |
References
| File | What it covers |
|---|---|
references/adapters-comparison.md |
detailed when-to-use-each-adapter matrix |
references/building-reference.md |
vite build options, preview, env vars |
references/routing-advanced-reference.md |
rest/optional/matchers/sort/encoding deep-dive |
references/layouts-advanced-reference.md |
nested/named/groups, reset, breaking out |
references/auth-reference.md |
sessions vs tokens, integration points, libs |
references/performance-reference.md |
full optimization checklist |
references/images-reference.md |
Vite + @sveltejs/enhanced-img reference |
references/a11y-reference.md |
accessibility patterns and resources |
references/seo-reference.md |
SEO setup, meta tags, structured data |
references/migration-v1-v2-reference.md |
full breaking-changes list |
references/migration-sapper-reference.md |
complete Sapper → SvelteKit migration |
references/debugging-reference.md |
IDE/debugger configs |