## docs/kit/10-getting-started/10-introduction.md # Introduction ## What is SvelteKit? Framework for building performant web apps using Svelte. Similar to Next (React) or Nuxt (Vue). ## What is Svelte? Compiler that converts UI components to JavaScript/CSS. Components render HTML and handle user interactions. ## SvelteKit vs Svelte **Svelte**: Renders UI components only. **SvelteKit**: Full-featured framework providing: - Router - [Build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) - [Offline support](service-workers) - [Preloading](link-options#data-sveltekit-preload-data) - [Configurable rendering](page-options): [SSR](glossary#SSR), [CSR](glossary#CSR), [prerendering](glossary#Prerendering) - [Image optimization](images) - HMR via [Vite](https://vitejs.dev/) + [Svelte plugin](https://github.com/sveltejs/vite-plugin-svelte) ## docs/kit/10-getting-started/20-creating-a-project.md # Creating a project ## Quick start ```sh npx sv create my-app cd my-app npm run dev ``` First command scaffolds project with optional TypeScript/tooling setup. `npm run dev` starts dev server on localhost:5173. ## Core concepts - Each page = Svelte component - Add files to `src/routes` to create pages - Server-rendered first visit → client-side app takeover ## Editor setup Use VS Code with Svelte extension (other editors supported). ## docs/kit/10-getting-started/25-project-types.md # Project Types SvelteKit offers configurable rendering. Rendering settings are not mutually exclusive - choose optimal rendering per route. Configuration is controlled by adapters and can be changed later. ## Default Rendering First page: [SSR](glossary#SSR). Subsequent pages: [CSR](glossary#CSR). SSR improves SEO and initial load. CSR updates pages without rerendering common components (faster, no flash). Also called transitional apps. ## Static Site Generation Use [`adapter-static`](adapter-static) for full [prerendering](glossary#Prerendering) or [prerender option](page-options#prerender) for specific pages with different adapter for others. For very large sites: use [ISR with `adapter-vercel`](adapter-vercel#Incremental-Static-Regeneration) to avoid long builds. ## Single-Page App [SPAs](glossary#SPA) use [CSR](glossary#CSR) exclusively. See [single-page apps](single-page-apps) docs. Skip `server` file docs if no backend or separate backend. ## Multi-Page App Not typical for SvelteKit. Use [`data-sveltekit-reload`](link-options#data-sveltekit-reload) for server rendering: `` or on specific links. Remove JS with [`csr = false`](page-options#csr). ## Separate Backend Backend in another language (Go, Java, PHP, Ruby, Rust, C#): - **Recommended**: Deploy frontend separately with `adapter-node` or serverless adapter - **Alternative**: Deploy as [SPA](single-page-apps) served by backend (worse SEO/performance) Skip `server` file docs. See [FAQ on backend API calls](faq#How-do-I-use-a-different-backend-API-server). ## Serverless App [`adapter-auto`](adapter-auto): zero-config for supported platforms Platform-specific: [`adapter-vercel`](adapter-vercel), [`adapter-netlify`](adapter-netlify), [`adapter-cloudflare`](adapter-cloudflare) [Community adapters](/packages#sveltekit-adapters) for other environments Some adapters offer `edge` option for [edge rendering](glossary#Edge). ## Your Own Server Use [`adapter-node`](adapter-node) for VPS/server deployment. ## Container Use [`adapter-node`](adapter-node) for Docker/LXC. ## Library Create library with [`@sveltejs/package`](packaging) add-on. Choose library option in [`sv create`](/docs/cli/sv-create). ## Offline App Full [service worker](service-workers) support for offline apps and [PWAs](glossary#PWA). ## Mobile App Turn [SPA](single-page-apps) into mobile app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/) or [Capacitor](https://capacitorjs.com/solution/svelte). Camera, geolocation, push notifications via plugins. Local web server serves app. Consider [`bundleStrategy: 'single'`](configuration#output) to limit requests (Capacitor uses HTTP/1). ## Desktop App Turn [SPA](single-page-apps) into desktop app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/), [Wails](https://wails.io/docs/guides/sveltekit/), or [Electron](https://www.electronjs.org/). ## Browser Extension Use [`adapter-static`](adapter-static) or [community adapters](/packages#sveltekit-adapters) for browser extensions. ## Embedded Device Svelte runs on low-power devices. For limited concurrent connections (microcontrollers, TVs), use [`bundleStrategy: 'single'`](configuration#output). ## docs/kit/10-getting-started/30-project-structure.md # Project Structure ## Directory Layout ```tree my-project/ ├ src/ │ ├ lib/ │ │ ├ server/ # Server-only code │ │ └ [lib files] # Utilities/components, import via $lib │ ├ params/ # Param matchers │ ├ routes/ # App routes │ ├ app.html # Page template │ ├ error.html # Error page │ ├ hooks.client.js │ ├ hooks.server.js │ ├ service-worker.js │ └ instrumentation.server.js ├ static/ # Static assets (robots.txt, etc) ├ tests/ # Playwright tests ├ package.json ├ svelte.config.js ├ tsconfig.json └ vite.config.js ``` ## Key Files ### src/app.html Page template with placeholders: - `%sveltekit.head%` — ``, `

{data.title}

{@html data.content}
``` With `params` (2.24+): ```svelte

{post.title}

{@html post.content}
``` ### +page.js Exports `load` function for data fetching. Runs on server and client. ```js /// file: src/routes/blog/[slug]/+page.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').PageLoad} */ export function load({ params }) { if (params.slug === 'hello-world') { return { title: 'Hello world!', content: 'Welcome to our blog. Lorem ipsum dolor sit amet...' }; } error(404, 'Not found'); } ``` Can export page options: `prerender`, `ssr`, `csr`. ### +page.server.js Server-only `load`. Use for database access, private env vars, etc. Change type to `PageServerLoad`. ```js /// file: src/routes/blog/[slug]/+page.server.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { const post = await getPostFromDatabase(params.slug); if (post) { return post; } error(404, 'Not found'); } ``` Return value must be serializable (uses devalue). Can export page options and actions. ## +error Customizes error page per route: ```svelte

{page.status}: {page.error.message}

``` SvelteKit walks up tree to find closest error boundary. Root fallback is `src/error.html`. **Note:** Not used for errors in `handle` or `+server.js` handlers. ## +layout ### +layout.svelte Wraps pages with shared UI. Must include `{@render children()}`. ```svelte {@render children()} ``` Layouts nest. Example with data: ```svelte

Settings

{@render children()} ``` ### +layout.js Loads data for layout: ```js /// file: src/routes/settings/+layout.js /** @type {import('./$types').LayoutLoad} */ export function load() { return { sections: [ { slug: 'profile', title: 'Profile' }, { slug: 'notifications', title: 'Notifications' } ] }; } ``` Data available to all child pages. Can export page options as defaults. ### +layout.server.js Server-only layout load. Change type to `LayoutServerLoad`. Can export page options. ## +server API routes. Export HTTP verb functions (`GET`, `POST`, etc.) returning `Response`. ```js /// file: src/routes/api/random-number/+server.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').RequestHandler} */ export function GET({ url }) { const min = Number(url.searchParams.get('min') ?? '0'); const max = Number(url.searchParams.get('max') ?? '1'); const d = max - min; if (isNaN(d) || d < 0) { error(400, 'min and max must be numbers, and min must be less than max'); } const random = min + Math.random() * d; return new Response(String(random)); } ``` First `Response` arg can be `ReadableStream` for streaming. **Notes:** - `+layout` files don't affect `+server.js` - `+error.svelte` not rendered for errors here - Use `handle` hook for logic before each request ### Receiving data ```svelte + = {total} ``` ```js /// file: src/routes/api/add/+server.js import { json } from '@sveltejs/kit'; /** @type {import('./$types').RequestHandler} */ export async function POST({ request }) { const { a, b } = await request.json(); return json(a + b); } ``` **Note:** Form actions are generally better for browser-to-server data submission. ### Fallback method handler Handles unhandled methods: ```js /// file: src/routes/api/add/+server.js import { json, text } from '@sveltejs/kit'; /** @type {import('./$types').RequestHandler} */ export async function POST({ request }) { const { a, b } = await request.json(); return json(a + b); } // This handler will respond to PUT, PATCH, DELETE, etc. /** @type {import('./$types').RequestHandler} */ export async function fallback({ request }) { return text(`I caught your ${request.method} request!`); } ``` ### Content negotiation When `+server.js` and `+page` coexist: - `PUT`/`PATCH`/`DELETE`/`OPTIONS` → always `+server.js` - `GET`/`POST`/`HEAD` → page if `accept` header prioritizes `text/html`, else `+server.js` - `GET` responses include `Vary: Accept` header ## $types SvelteKit generates `$types.d.ts` for type safety. ```svelte ``` Types: `PageProps`, `LayoutProps` (2.16.0+), `PageLoad`, `PageServerLoad`, `LayoutLoad`, `LayoutServerLoad`. IDE tooling auto-inserts types—manual annotation optional. ## Other files Non-route files in route directories are ignored. Colocate components/modules with routes. For shared code, use `$lib`. ## docs/kit/20-core-concepts/20-load.md # Loading data ## Page data `+page.js` exports a `load` function whose return value is available to `+page.svelte` via `data` prop: ```js /// file: src/routes/blog/[slug]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ params }) { return { post: { title: `Title for ${params.slug} goes here`, content: `Content for ${params.slug} goes here` } }; } ``` ```svelte

{data.post.title}

{@html data.post.content}
``` `+page.js` runs on server and browser. For server-only (database access, private env vars), use `+page.server.js`: ```js /// file: src/routes/blog/[slug]/+page.server.js import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { post: await db.getPost(params.slug) }; } ``` ## Layout data `+layout.js` or `+layout.server.js` loads data for layouts: ```js /// file: src/routes/blog/[slug]/+layout.server.js import * as db from '$lib/server/database'; /** @type {import('./$types').LayoutServerLoad} */ export async function load() { return { posts: await db.getPostSummaries() }; } ``` ```svelte
{@render children()}
``` Layout data is available to child layouts and pages. If multiple `load` functions return same key, last one wins. ## page.data Parent layouts can access page/child data via `page.data`: ```svelte {page.data.title} ``` ## Universal vs server **Server load** (`+page.server.js`, `+layout.server.js`): - Always runs server-side - Access to `clientAddress`, `cookies`, `locals`, `platform`, `request` - Must return serializable data (devalue) **Universal load** (`+page.js`, `+layout.js`): - Runs on server during SSR, then in browser during hydration - Subsequent runs in browser only - Can return non-serializable values (classes, components) - Has `data` property containing server load return value **When to use:** - Server: database access, private credentials, filesystem - Universal: external API without credentials, non-serializable returns ## Using URL data ### url `URL` instance with `origin`, `hostname`, `pathname`, `searchParams`. `url.hash` unavailable during load. ### route Current route directory relative to `src/routes`: ```js /// file: src/routes/a/[b]/[...c]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ route }) { console.log(route.id); // '/a/[b]/[...c]' } ``` ### params Derived from `url.pathname` and `route.id`. For `/a/[b]/[...c]` and `/a/x/y/z`: ```json { "b": "x", "c": "y/z" } ``` ## Making fetch requests Provided `fetch` function: - Inherits `cookie` and `authorization` headers on server - Makes relative requests on server - Internal requests go directly to handler (no HTTP overhead) - Response captured and inlined during SSR - Response read from HTML during hydration ```js /// file: src/routes/items/[id]/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, params }) { const res = await fetch(`/api/items/${params.id}`); const item = await res.json(); return { item }; } ``` ## Cookies Server `load` can get/set cookies: ```js /// file: src/routes/+layout.server.js import * as db from '$lib/server/database'; /** @type {import('./$types').LayoutServerLoad} */ export async function load({ cookies }) { const sessionid = cookies.get('sessionid'); return { user: await db.getUser(sessionid) }; } ``` Cookies passed through `fetch` only if target host is same or subdomain. ## Headers `setHeaders` sets response headers (server only): ```js /// file: src/routes/products/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, setHeaders }) { const url = `https://cms.example.com/products.json`; const response = await fetch(url); setHeaders({ age: response.headers.get('age'), 'cache-control': response.headers.get('cache-control') }); return response.json(); } ``` Can't set same header multiple times. Use `cookies.set()` for `set-cookie`. ## Using parent data Access parent `load` data with `await parent()`: ```js /// file: src/routes/+layout.js /** @type {import('./$types').LayoutLoad} */ export function load() { return { a: 1 }; } ``` ```js /// file: src/routes/abc/+layout.js /** @type {import('./$types').LayoutLoad} */ export async function load({ parent }) { const { a } = await parent(); return { b: a + 1 }; } ``` ```js /// file: src/routes/abc/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ parent }) { const { a, b } = await parent(); return { c: a + b }; } ``` Avoid waterfalls - call independent functions before `await parent()`. ## Errors Use `error` helper for expected errors: ```js /// file: src/routes/admin/+layout.server.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { error(401, 'not logged in'); } if (!locals.user.isAdmin) { error(403, 'not an admin'); } } ``` Calling `error(...)` throws exception. Unexpected errors treated as 500. ## Redirects Use `redirect` helper: ```js /// file: src/routes/user/+layout.server.js import { redirect } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { redirect(307, '/login'); } } ``` Don't use inside `try {...}` blocks. In browser, use `goto` from `$app/navigation`. ## Streaming with promises Server `load` promises stream to browser as they resolve: ```js /// file: src/routes/blog/[slug]/+page.server.js /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { comments: loadComments(params.slug), // not awaited post: await loadPost(params.slug) }; } ``` ```svelte

{data.post.title}

{@html data.post.content}
{#await data.comments} Loading comments... {:then comments} {#each comments as comment}

{comment.content}

{/each} {:catch error}

error loading comments: {error.message}

{/await} ``` Handle rejections to avoid unhandled promise errors. Attach noop-`catch` or use SvelteKit's `fetch`. **Limitations:** - Doesn't work without JavaScript - Can't `setHeaders` or redirect inside streamed promise - Not supported on some platforms (AWS Lambda, Firebase) ## Parallel loading All `load` functions run concurrently. Server loads grouped into single response during client navigation. ## Rerunning load functions `load` reruns when: - Referenced `params` property changes - Referenced `url` property changes (pathname, search, searchParams) - `await parent()` called and parent reruns - Dependency marked invalid via `fetch(url)` or `depends(url)` + `invalidate(url)` - `invalidateAll()` called ### Untracking dependencies ```js /// file: src/routes/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ untrack, url }) { if (untrack(() => url.pathname === '/')) { return { message: 'Welcome!' }; } } ``` ### Manual invalidation ```js /// file: src/routes/random-number/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, depends }) { const response = await fetch('https://api.example.com/random-number'); depends('app:random'); return { number: await response.json() }; } ``` ```svelte

random number: {data.number}

``` ## Implications for authentication - Layout `load` doesn't run on every request - Layout and page `load` run concurrently unless `await parent()` called **Auth strategies:** - Use hooks to protect routes before `load` runs - Put guards in `+page.server.js` for route-specific protection - Avoid guards in `+layout.server.js` unless all children call `await parent()` ## Using getRequestEvent Access request event in shared logic: ```js /// file: src/lib/server/auth.js import { redirect } from '@sveltejs/kit'; import { getRequestEvent } from '$app/server'; export function requireLogin() { const { locals, url } = getRequestEvent(); if (!locals.user) { const redirectTo = url.pathname + url.search; const params = new URLSearchParams({ redirectTo }); redirect(303, `/login?${params}`); } return locals.user; } ``` ```js /// file: +page.server.js import { requireLogin } from '$lib/server/auth'; export function load() { const user = requireLogin(); return { message: `hello ${user.name}!` }; } ``` ## docs/kit/20-core-concepts/30-form-actions.md # Form Actions A `+page.server.js` file exports _actions_ to `POST` data to the server using `
`. Works without JavaScript, can be progressively enhanced. ## Default Actions ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in } }; ``` ```svelte
``` Invoke from other pages with `action` attribute: ```html /// file: src/routes/+layout.svelte
``` > Actions always use `POST` requests. ## Named Actions ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { login: async (event) => { // TODO log the user in }, register: async (event) => { // TODO register the user } }; ``` Invoke with query parameter prefixed by `/`: ```svelte
``` ```svelte ``` Use `formaction` on buttons: ```svelte /// file: src/routes/login/+page.svelte
``` > Can't have default actions next to named actions - query parameter persists in URL. ## Anatomy of an Action Actions receive `RequestEvent`, read data with `request.formData()`. Return data available via `form` prop and `page.form`. ```js /// file: src/routes/login/+page.server.js import * as db from '$lib/server/db'; /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ```svelte {#if form?.success}

Successfully logged in! Welcome back, {data.user.name}

{/if} ``` ### Validation Errors Use `fail()` to return HTTP status code (400/422) with validation errors: ```js /// file: src/routes/login/+page.server.js import { fail } from '@sveltejs/kit'; import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); if (!email) { return fail(400, { email, missing: true }); } const user = await db.getUser(email); if (!user || user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ```svelte /// file: src/routes/login/+page.svelte
{#if form?.missing}

The email field is required

{/if} {#if form?.incorrect}

Invalid credentials!

{/if}
``` ### Redirects ```js /// file: src/routes/login/+page.server.js import { fail, redirect } from '@sveltejs/kit'; import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request, url }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); if (!user) { return fail(400, { email, missing: true }); } if (user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); if (url.searchParams.has('redirectTo')) { redirect(303, url.searchParams.get('redirectTo')); } return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ## Loading Data After action runs, page re-renders with action's return value as `form` prop. Page `load` functions run after action completes. `handle` runs before action and doesn't rerun before `load`. Update `event.locals` in action if you set/delete cookies: ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUser(event.cookies.get('sessionid')); return resolve(event); } ``` ```js /// file: src/routes/account/+page.server.js /** @type {import('./$types').PageServerLoad} */ export function load(event) { return { user: event.locals.user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { logout: async (event) => { event.cookies.delete('sessionid', { path: '/' }); event.locals.user = null; } }; ``` ## Progressive Enhancement ### use:enhance ```svelte /// file: src/routes/login/+page.svelte
``` > Only works with `method="POST"` pointing to `+page.server.js` actions. Without arguments, `use:enhance` emulates browser behavior without full-page reloads: - Updates `form`, `page.form`, `page.status` on success/invalid response (only if action on same page) - Resets `` element - Invalidates all data with `invalidateAll` on success - Calls `goto` on redirect - Renders nearest `+error` boundary on error - Resets focus ### Customising use:enhance ```svelte { // `formElement` is this `` element // `formData` is its `FormData` object that's about to be submitted // `action` is the URL to which the form is posted // calling `cancel()` will prevent the submission // `submitter` is the `HTMLElement` that caused the form to be submitted return async ({ result, update }) => { // `result` is an `ActionResult` object // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set }; }} > ``` Call `update` to get default behavior, or use `applyAction`: ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` is an `ActionResult` object if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); } }; }} > ``` `applyAction(result)` behavior: - `success`, `failure` — sets `page.status` to `result.status`, updates `form` and `page.form` to `result.data` - `redirect` — calls `goto(result.location, { invalidateAll: true })` - `error` — renders nearest `+error` boundary with `result.error` ### Custom Event Listener ```svelte
``` Use `deserialize()` not `JSON.parse()` - supports `Date`/`BigInt`. If `+server.js` exists alongside `+page.server.js`, add `x-sveltekit-action` header: ```js const response = await fetch(this.action, { method: 'POST', body: data, headers: { 'x-sveltekit-action': 'true' } }); ``` ## Alternatives Can use `+server.js` for JSON API: ```svelte ``` ```js /// file: src/routes/api/ci/+server.js /** @type {import('./$types').RequestHandler} */ export function POST() { // do something } ``` ## GET vs POST Use `method="GET"` for forms that don't POST data (e.g., search). SvelteKit treats them like `` elements using client-side router: ```html
``` Submitting navigates to `/search?q=...`, invokes `load` but not action. Can use `data-sveltekit-*` attributes to control router behavior. ## docs/kit/20-core-concepts/40-page-options.md # Page Options SvelteKit renders components server-side, sends HTML to client, then hydrates for interactivity. Control this per-page via `+page.js`/`+page.server.js` or per-group via `+layout.js`/`+layout.server.js`. Child layouts/pages override parent values. ## prerender Generate static HTML at build time. ```js /// file: +page.js/+page.server.js/+server.js export const prerender = true; ``` Or set in root layout and opt-out specific pages: ```js /// file: +page.js/+page.server.js/+server.js export const prerender = false; ``` Third option excludes from SSR manifest but still prerenders: ```js /// file: +page.js/+page.server.js/+server.js export const prerender = 'auto'; ``` Prerenderer starts at root, follows `
` links. Configure entry points via `config.kit.prerender.entries` or [`entries`](#entries) function. ### Prerendering server routes `+server.js` files inherit `prerender` from pages that fetch from them unless explicitly set. ```js /// file: +page.js export const prerender = true; /** @type {import('./$types').PageLoad} */ export async function load({ fetch }) { const res = await fetch('/my-server-route.json'); return await res.json(); } ``` ### When not to prerender **Rule:** Two users hitting a page directly must get identical server content. - Don't prerender personalized content - `url.searchParams` forbidden during prerender (use in browser only, e.g. `onMount`) - Pages with [actions](form-actions) can't be prerendered ### Route conflicts Avoid directory/file name conflicts. Use file extensions: `foo.json/+server.js` and `foo/bar.json/+server.js` create `foo.json` and `foo/bar.json`. Pages write `foo/index.html`. ### Troubleshooting Error "routes marked as prerenderable, but were not prerendered": - Ensure crawler finds route via `config.kit.prerender.entries` or [`entries`](#entries) - Add links to dynamic routes with `[parameters]` - Change to `export const prerender = 'auto'` for dynamic SSR fallback ## entries Define which dynamic routes to prerender: ```js /// file: src/routes/blog/[slug]/+page.server.js /** @type {import('./$types').EntryGenerator} */ export function entries() { return [ { slug: 'hello-world' }, { slug: 'another-blog-post' } ]; } export const prerender = true; ``` Can be `async` to fetch from CMS/database. ## ssr Disable server-side rendering: ```js /// file: +page.js export const ssr = false; // If both `ssr` and `csr` are `false`, nothing will be rendered! ``` Renders empty shell. Not recommended for most cases. In root `+layout.js` turns app into SPA. **Note:** If all page options are boolean/string literals, SvelteKit evaluates statically. Otherwise imports module on server, so avoid browser-only code at module level. ## csr Disable client-side rendering (no JavaScript shipped): ```js /// file: +page.js export const csr = false; // If both `csr` and `ssr` are `false`, nothing will be rendered! ``` Effects: - Page works with HTML/CSS only - ` ``` ```svelte

Welcome {user().name}

``` > Pass a function to `setContext` to maintain reactivity. See [$state docs](/docs/svelte/$state#Passing-state-into-functions). **Gotcha:** Updating context state in child components during SSR won't affect already-rendered parents. Values may "flash" during hydration. Prefer passing state down. Without SSR, you can use shared modules directly. ## Component state is preserved Components are reused during navigation. State doesn't automatically reset. ```svelte

{data.title}

Reading time: {Math.round(estimatedReadingTime)} minutes

{@html data.content}
``` ✅ Make values reactive: ```svelte /// file: src/routes/blog/[slug]/+page.svelte ``` > Use [afterNavigate]($app-navigation#afterNavigate) and [beforeNavigate]($app-navigation#beforeNavigate) if you need `onMount`/`onDestroy` behavior on navigation. To force component remount: ```svelte {#key page.url.pathname} {/key} ``` ## Storing state in the URL For state that should survive reloads/affect SSR (filters, sorting): - Use URL search params: `?sort=price&order=ascending` - Set via `
`, `
`, or `goto('?key=value')` - Access in `load` via `url` parameter, in components via `page.url.searchParams` ## Storing ephemeral state in snapshots For disposable UI state (accordion open/closed) that should persist across navigation but not reloads, use [snapshots](snapshots) to associate state with history entries. ## docs/kit/20-core-concepts/60-remote-functions.md # Remote Functions **Experimental feature** - requires config: ```js /// file: svelte.config.js const config = { kit: { experimental: { remoteFunctions: true } }, compilerOptions: { experimental: { async: true } } }; ``` Type-safe client-server communication. Always run on server, can access server-only modules. Export from `.remote.js`/`.remote.ts` files. ## query Read dynamic data from server. Cannot be used on prerendered pages. ```js /// file: src/routes/blog/data.remote.js import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { const posts = await db.sql`SELECT title, slug FROM post ORDER BY published_at DESC`; return posts; }); ``` ```svelte ``` Alternative to `await` - use `loading`, `error`, `current` properties: ```svelte {#if query.error}

oops!

{:else if query.loading}

loading...

{:else} {/if} ``` ### Query arguments Validate with Standard Schema (Zod/Valibot): ```js import * as v from 'valibot'; import { error } from '@sveltejs/kit'; import { query } from '$app/server'; export const getPost = query(v.string(), async (slug) => { const [post] = await db.sql`SELECT * FROM post WHERE slug = ${slug}`; if (!post) error(404, 'Not found'); return post; }); ``` ```svelte

{post.title}

``` Arguments/returns serialized with devalue (handles `Date`, `Map`, custom types). Objects/maps/sets sorted for cache keys. ### Deduplication Queries cached per request (server) and shared across components (client). Cache released when no longer in use. ### Refreshing queries ```svelte ``` ## query.batch Batches requests in same macrotask (solves n+1 problem): ```js import * as v from 'valibot'; import { query } from '$app/server'; export const getWeather = query.batch(v.string(), async (cityIds) => { const weather = await db.sql`SELECT * FROM weather WHERE city_id = ANY(${cityIds})`; const lookup = new Map(weather.map(w => [w.city_id, w])); return (cityId) => lookup.get(cityId); }); ``` ```svelte {#each cities as city} {/each} ``` ## query.live Real-time data via async generator: ```js import { query } from '$app/server'; export const getTime = query.live(async function* () { while (true) { yield new Date(); await new Promise((f) => setTimeout(f, 1000)); } }); ``` SSR returns first value. Client stays connected while in use. Exposes `connected` property and `reconnect()` method: ```svelte

{await time}

connected: {time.connected}

``` Can iterate directly: `for await (const value of getTime()) { ... }` No `refresh()` method (self-updating). Don't cache in service workers. ## form Write data to server: ```js /// file: src/routes/blog/data.remote.js import * as v from 'valibot'; import { error, redirect } from '@sveltejs/kit'; import { form } from '$app/server'; import * as auth from '$lib/server/auth'; export const createPost = form( v.object({ title: v.pipe(v.string(), v.nonEmpty()), content: v.pipe(v.string(), v.nonEmpty()) }), async ({ title, content }) => { const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); const slug = title.toLowerCase().replace(/ /g, '-'); await db.sql`INSERT INTO post (slug, title, content) VALUES (${slug}, ${title}, ${content})`; redirect(303, `/blog/${slug}`); } ); ``` ```svelte ``` Works without JS (submits/reloads). Progressively enhanced when JS available. ### Fields Use `.as(type, value?)` for field attributes: ```svelte
``` Second argument sets value/default. Required for `radio`, `submit`, `hidden`. Needed for `checkbox` in arrays. Nested objects/arrays supported: ```js const datingProfile = v.object({ name: v.string(), photo: v.file(), info: v.object({ height: v.number(), likesDogs: v.optional(v.boolean(), false) }), attributes: v.array(v.string()) }); export const createProfile = form(datingProfile, (data) => { /* ... */ }); ``` ```svelte
``` Radio/checkbox groups need value specified: ```svelte {#each operatingSystems as os} {/each} ``` Or use `select`: ```svelte ``` ### Programmatic validation Use `invalid` helper for runtime validation: ```js import * as v from 'valibot'; import { invalid } from '@sveltejs/kit'; import { form } from '$app/server'; export const buyHotcakes = form( v.object({ qty: v.pipe(v.number(), v.minValue(1, 'you must buy at least one hotcake')) }), async (data, issue) => { try { await db.buy(data.qty); } catch (e) { if (e.code === 'OUT_OF_STOCK') { invalid(issue.qty(`we don't have enough hotcakes`)); } } } ); ``` ### Validation Show issues per field: ```svelte
``` Validate programmatically: ```svelte
createPost.validate()}> ``` Client-side preflight validation: ```svelte ``` All issues: `createPost.fields.allIssues()` ### Getting/setting inputs Get current value: `createPost.fields.title.value()` Set value: `createPost.fields.set({ title: '...', content: '...' })` ### Handling sensitive data Prefix with `_` to prevent sending back to user: ```svelte ``` ### Returns and redirects Return data instead of redirecting: ```js export const createPost = form( v.object({/* ... */}), async (data) => { // ... return { success: true }; } ); ``` ```svelte {#if createPost.result?.success}

Successfully published!

{/if} ``` ### enhance Customize submission: ```svelte { try { if (await form.submit()) { form.element.reset(); showToast('Successfully published!'); } else { showToast('Invalid data!'); } } catch (error) { showToast('Oh no! Something went wrong'); } })}> ``` Must manually reset: `form.element.reset()` ### Multiple instances Use `.for(id)` for isolation: ```svelte {#each await getTodos() as todo} {@const modify = modifyTodo.for(todo.id)}
{/each} ``` ### Multiple submit buttons ```svelte
``` ```js export const loginOrRegister = form( v.object({ username: v.string(), _password: v.string(), action: v.picklist(['login', 'register']) }), async ({ username, _password, action }) => { if (action === 'login') { /* ... */ } else { /* ... */ } } ); ``` ## command Like `form` but callable from anywhere (not element-specific): ```js import * as v from 'valibot'; import { query, command } from '$app/server'; export const getLikes = query(v.string(), async (id) => { const [row] = await db.sql`SELECT likes FROM item WHERE id = ${id}`; return row.likes; }); export const addLike = command(v.string(), async (id) => { await db.sql`UPDATE item SET likes = likes + 1 WHERE id = ${id}`; }); ``` ```svelte ``` Cannot be called during render. ## Single-flight mutations Refresh queries in same request as mutation. ### Server-driven refreshes ```js export const createPost = form( v.object({/* ... */}), async (data) => { // ... void getPosts().refresh(); redirect(303, `/blog/${slug}`); } ); export const updatePost = form( v.object({ id: v.string() }), async (post) => { const result = externalApi.update(post); getPost(post.id).set(result); } ); ``` ### Reconnecting live queries ```js export const markAllRead = form(v.object({ userId: v.string() }), async ({ userId }) => { // mutation logic... getNotifications(userId).reconnect(); }); ``` ### Client-requested refreshes Client requests specific updates: ```js await submit().updates( getPosts, getPosts({ filter: 'author:santa' }), getPosts({ filter: 'author:santa' }).withOverride((posts) => [newPost, ...posts]) ); ``` Server accepts with `requested`: ```js import { query, form, requested } from '$app/server'; export const createPost = form( v.object({/* ... */}), async (data) => { // ... for (const { query } of requested(getPosts, 1)) { void query.refresh(); } redirect(303, `/blog/${slug}`); } ); ``` Shorthand: `await requested(getPosts, 1).refreshAll();` `limit` required (DoS protection). ## prerender Like `query` but runs at build time: ```js import { prerender } from '$app/server'; export const getPosts = prerender(async () => { const posts = await db.sql`SELECT title, slug FROM post ORDER BY published_at DESC`; return posts; }); ``` Cached using Cache API, survives reloads, cleared on new deployment. ### Prerender arguments ```js export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { inputs: () => ['first-post', 'second-post', 'third-post'] } ); ``` Allow dynamic calls: `{ dynamic: true }` ## Handling validation errors Implement `handleValidationError` hook: ```js /// file: src/hooks.server.js export function handleValidationError({ event, issues }) { return { message: 'Nice try, hacker!' }; } ``` Opt out: `query('unchecked', async ({ id }: { id: string }) => { /* ... */ })` ## Using `getRequestEvent` Access `RequestEvent` inside remote functions: ```js import { getRequestEvent, query } from '$app/server'; export const getProfile = query(async () => { const user = await getUser(); return user && { name: user.name, avatar: user.avatar }; }); const getUser = query(async () => { const { cookies } = getRequestEvent(); return await findUser(cookies.get('session_id')); }); ``` Note: `route`, `params`, `url` relate to calling page, not endpoint. Don't use for auth. ## Redirects `redirect()` works in `query`, `form`, `prerender`. Not in `command`. ## docs/kit/20-core-concepts/70-environment-variables.md # Environment Variables Environment variables store sensitive info (API keys, DB credentials) outside source code. ## Setup Variables in `.env` or `.env.local` are available during dev and build: ```env /// file: .env.local API_KEY=19f401ba-e8b0-48c4-8c77-b0ebb26d97fe ``` ## Explicit Environment Variables (SvelteKit 2.63+) **Will be default in SvelteKit 3.** Old `$env/*` and `$app/environment` modules will be removed. ### Configuration ```js /// file: svelte.config.js export default { kit: { experimental: { explicitEnvironmentVariables: true } } }; ``` Create `src/env.ts`: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; export const variables = defineEnvVars({ // ... }); ``` `defineEnvVars` returns its argument unaltered — exists for type safety. ### Private Variables Default. Cannot be imported in browser code: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; export const variables = defineEnvVars({ API_KEY: {} }); ``` ```js import { API_KEY } from '$app/env/private'; ``` ### Public Variables Set `public: true` to expose to browser: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; export const variables = defineEnvVars({ GOOGLE_ANALYTICS_ID: { public: true } }); ``` Use in `app.html` as `%sveltekit.env.GOOGLE_ANALYTICS_ID%`: ```html %sveltekit.head%
%sveltekit.body%
``` ### Validation Use [Standard Schema](https://standardschema.dev/) validators (Zod, Valibot): ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; import * as v from 'valibot'; export const variables = defineEnvVars({ GOOGLE_ANALYTICS_ID: { public: true, schema: v.pipe(v.string(), v.regex(/G-[A-Z0-9]+/)) } }); ``` Invalid values prevent app start/build. Make optional during build: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; import { building } from '$app/env' import * as v from 'valibot'; export const variables = defineEnvVars({ SECRET: { // optional when building but required when starting the app schema: building ? v.optional(v.string()) : v.string() } }); ``` Use validators to make optional or transform values (string to boolean, parse JSON). ### Static Variables Set `static: true` to inline into code for dead-code elimination: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; import * as v from 'valibot'; export const variables = defineEnvVars({ SHOW_DEBUG_OVERLAY: { public: true, static: true, // coerce to true/false schema: v.pipe( v.optional(v.string(), ''), v.transform((str) => str !== '') ) } }); ``` Component excluded from bundle unless variable is truthy: ```svelte {#if SHOW_DEBUG_OVERLAY} {/if} ``` Set before build to include: ```bash SHOW_DEBUG_OVERLAY=true npm run build ``` ### Documentation Add `description` for hover tooltips: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/env'; export const variables = defineEnvVars({ CACHE_TTL_SECONDS: { description: 'How long to cache responses, in seconds' } }); ``` ## Legacy Modules (Pre-2.63) - `$env/static/private` - `$env/static/public` - `$env/dynamic/private` - `$env/dynamic/public` ## docs/kit/25-build-and-deploy/10-building-your-app.md # Building your app Build happens in two stages when running `vite build`: 1. Vite creates optimized production build (server code, browser code, service worker). Prerendering executes here. 2. Adapter tunes build for target environment. ## During the build Files are loaded for analysis during build. Code that shouldn't execute at build time must check `building`: ```js import { building } from '$app/environment'; import { initialiseDatabase } from '$lib/server/database'; if (!building) { initialiseDatabase(); } export function load() { // ... } ``` ## Preview your app View production build locally with `vite preview`. Runs in Node - not perfect reproduction (adapter-specific features like `platform` object don't apply). ## docs/kit/25-build-and-deploy/20-adapters.md # Adapters Adapters are plugins that prepare your SvelteKit app for deployment to specific platforms. ## Official Adapters - `@sveltejs/adapter-cloudflare` - Cloudflare Workers/Pages - `@sveltejs/adapter-netlify` - Netlify - `@sveltejs/adapter-node` - Node servers - `@sveltejs/adapter-static` - Static site generation (SSG) - `@sveltejs/adapter-vercel` - Vercel Community adapters available at `/packages#sveltekit-adapters`. ## Usage Configure in `svelte.config.js`: ```js /// file: svelte.config.js // @filename: ambient.d.ts declare module 'svelte-adapter-foo' { const adapter: (opts: any) => import('@sveltejs/kit').Adapter; export default adapter; } // @filename: index.js //cut import adapter from 'svelte-adapter-foo'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ // adapter options go here }) } }; export default config; ``` ## Platform-Specific Context Adapters may provide platform-specific data (e.g., Cloudflare KV namespaces) via the `platform` property in `RequestEvent` (available in hooks and server routes). See adapter docs for details. ## docs/kit/25-build-and-deploy/55-single-page-apps.md # Single-page apps Turn SvelteKit into a client-rendered SPA by specifying a fallback page that serves URLs not handled by prerendered pages. > [!NOTE] SPA mode forces multiple network round trips (HTML → JS → data) before showing content, harming performance and SEO. Prerender as many pages as possible, especially your homepage. If all pages can be prerendered, use [static site generation](adapter-static) instead. ## Usage Disable SSR for non-prerendered pages: ```js /// file: src/routes/+layout.js export const ssr = false; ``` If no server-side logic (`+page.server.js`, `+layout.server.js`, `+server.js`), use `adapter-static`: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ fallback: '200.html' // may differ from host to host }) } }; export default config; ``` The `fallback` page is an HTML page that loads your app and navigates to the correct route. Consult your platform's docs for the correct filename (e.g. `200.html` for Surge). Avoid `index.html` as it may conflict with prerendering. > [!NOTE] Fallback page always contains absolute asset paths (starting with `/`) regardless of `paths.relative`. ## Prerendering individual pages Re-enable `ssr` + `prerender` for specific pages: ```js /// file: src/routes/my-prerendered-page/+page.js export const prerender = true; export const ssr = true; ``` No Node server needed—pages are server-rendered at build time to static `.html` files. ## Apache Add `static/.htaccess` to route requests to fallback: ``` RewriteEngine On RewriteBase / RewriteRule ^200\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /200.html [L] ``` ## docs/kit/30-advanced/10-advanced-routing.md # Advanced Routing ## Rest Parameters Unknown number of segments: ```sh /[org]/[repo]/tree/[branch]/[...file] ``` Request `/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md` gives: ```js { org: 'sveltejs', repo: 'kit', branch: 'main', file: 'documentation/docs/04-advanced-routing.md' } ``` > `src/routes/a/[...rest]/z/+page.svelte` matches `/a/z`, `/a/b/z`, `/a/b/c/z`, etc. Validate rest parameter values using matchers. ### 404 Pages Create catch-all route for custom 404s: ```tree src/routes/ ├ marx-brothers/ | ├ [...path]/ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ │ └ +error.svelte └ +error.svelte ``` ```js /// file: src/routes/marx-brothers/[...path]/+page.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').PageLoad} */ export function load(event) { error(404, 'Not Found'); } ``` > Unhandled 404s appear in `handleError` ## Optional Parameters `[[lang]]/home` matches both `home` and `en/home`. > Cannot follow rest parameter: `[...rest]/[[optional]]` is invalid ## Matching Validate parameters with matchers in `src/params`: ```js /// file: src/params/fruit.js // @errors: 1360 /** * @param {string} param * @return {param is ('apple' | 'orange')} * @satisfies {import('@sveltejs/kit').ParamMatcher} */ export function match(param) { return param === 'apple' || param === 'orange'; } ``` Use in routes: ``` src/routes/fruits/[page=fruit] ``` > Matchers run on server and browser. `*.test.js` and `*.spec.js` files ignored. ## Sorting Multiple routes can match same path. Priority order: 1. More specific routes (fewer parameters) 2. Parameters with matchers `[name=type]` over `[name]` 3. `[[optional]]` and `[...rest]` lowest priority (ignored unless final segment) 4. Alphabetical for ties Example order: ```sh src/routes/foo-abc/+page.svelte src/routes/foo-[c]/+page.svelte src/routes/[[a=x]]/+page.svelte src/routes/[b]/+page.svelte src/routes/[...catchall]/+page.svelte ``` ## Encoding Use hex escapes `[x+nn]` for special characters: - `\` — `[x+5c]` - `/` — `[x+2f]` - `:` — `[x+3a]` - `*` — `[x+2a]` - `?` — `[x+3f]` - `"` — `[x+22]` - `<` — `[x+3c]` - `>` — `[x+3e]` - `|` — `[x+7c]` - `#` — `[x+23]` - `%` — `[x+25]` - `[` — `[x+5b]` - `]` — `[x+5d]` - `(` — `[x+28]` - `)` — `[x+29]` Example: `/smileys/:-)` → `src/routes/smileys/[x+3a]-[x+29]/+page.svelte` Unicode escapes `[u+nnnn]` also supported: ``` src/routes/[u+d83e][u+dd2a]/+page.svelte src/routes/🤪/+page.svelte ``` > Use `[x+2e]` for `.well-known` routes (TypeScript compatibility) ## Advanced Layouts ### (group) Group routes without affecting URLs: ```tree src/routes/ │ (app)/ │ ├ dashboard/ │ ├ item/ │ └ +layout.svelte │ (marketing)/ │ ├ about/ │ ├ testimonials/ │ └ +layout.svelte ├ admin/ └ +layout.svelte ``` `+page` can be directly inside `(group)`. ### Breaking Out of Layouts Routes outside groups (like `/admin`) don't inherit group layouts. ### +page@ Break out of layouts per-page with `@segment`: ```tree src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ │ │ │ │ └ +page@(app).svelte │ │ │ └ +layout.svelte │ │ └ +layout.svelte │ └ +layout.svelte └ +layout.svelte ``` Options: - `+page@[id].svelte` - inherits from `[id]/+layout.svelte` - `+page@item.svelte` - inherits from `item/+layout.svelte` - `+page@(app).svelte` - inherits from `(app)/+layout.svelte` - `+page@.svelte` - inherits from root layout ### +layout@ Layouts can also break out: ``` src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ │ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte │ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte │ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte │ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte │ └ +layout.svelte └ +layout.svelte ``` ### When to Use Layout Groups Alternative: reusable components/functions: ```svelte {@render children()} ``` ```js /// file: src/routes/nested/route/+layout.js // @filename: ambient.d.ts declare module "$lib/reusable-load-function" { export function reusableLoad(event: import('@sveltejs/kit').LoadEvent): Promise>; } // @filename: index.js //cut import { reusableLoad } from '$lib/reusable-load-function'; /** @type {import('./$types').PageLoad} */ export function load(event) { // Add additional logic here, if needed return reusableLoad(event); } ``` ## docs/kit/30-advanced/20-hooks.md # Hooks App-wide functions SvelteKit calls in response to specific events. Three optional files: - `src/hooks.server.js` — server hooks - `src/hooks.client.js` — client hooks - `src/hooks.js` — runs on both client and server Code runs at app startup, useful for initializing database clients. ## handle **Location:** `src/hooks.server.js` Runs on every server request (including prerendering). Receives `event` and `resolve` function. Modify responses or bypass SvelteKit. ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { if (event.url.pathname.startsWith('/custom')) { return new Response('custom response'); } const response = await resolve(event); return response; } ``` **Note:** Static assets and prerendered pages not handled by SvelteKit. **Warning:** For remote function requests, `route`, `params`, `url` relate to the calling page, not the endpoint. Don't use for authorization checks. Default: `({ event, resolve }) => resolve(event)` Use [`sequence`](@sveltejs-kit-hooks) for multiple `handle` functions. ### resolve options Second parameter to `resolve`: - `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise` — transform HTML chunks - `filterSerializedResponseHeaders(name: string, value: string): boolean` — filter headers in serialized responses (default: none) - `preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean` — determine preloaded files (default: `js` and `css`) ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { const response = await resolve(event, { transformPageChunk: ({ html }) => html.replace('old', 'new'), filterSerializedResponseHeaders: (name) => name.startsWith('x-'), preload: ({ type, path }) => type === 'js' || path.includes('/important/') }); return response; } ``` **Note:** `resolve(...)` never throws, always returns `Promise`. Errors elsewhere are fatal. Customize via `src/error.html`. ### locals Add custom data to `event.locals` for handlers and server `load` functions: ```js /// file: src/hooks.server.js // @filename: ambient.d.ts type User = { name: string; } declare namespace App { interface Locals { user: User; } } const getUserInformation: (cookie: string | void) => Promise; // @filename: index.js //cut /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUserInformation(event.cookies.get('sessionid')); const response = await resolve(event); // Note that modifying response headers isn't always safe. // Response objects can have immutable headers // (e.g. Response.redirect() returned from an endpoint). // Modifying immutable headers throws a TypeError. // In that case, clone the response or avoid creating a // response object with immutable headers. response.headers.set('x-custom-header', 'potato'); return response; } ``` ## handleFetch **Location:** `src/hooks.server.js` Modify/replace `event.fetch` calls on server (or during prerendering) inside endpoints, `load`, `action`, `handle`, `handleError`, or `reroute`. ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').HandleFetch} */ export async function handleFetch({ request, fetch }) { if (request.url.startsWith('https://api.yourapp.com/')) { // clone the original request, but change the URL request = new Request( request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'), request ); } return fetch(request); } ``` **Credentials:** Same-origin requests forward `cookie`/`authorization` unless `credentials: "omit"`. Cross-origin includes cookies for subdomains. **Caveat:** Sibling subdomains (`www.my-domain.com` and `api.my-domain.com`) don't share parent domain cookies automatically: ```js /// file: src/hooks.server.js // @errors: 2345 /** @type {import('@sveltejs/kit').HandleFetch} */ export async function handleFetch({ event, request, fetch }) { if (request.url.startsWith('https://api.my-domain.com/')) { request.headers.set('cookie', event.request.headers.get('cookie')); } return fetch(request); } ``` ## handleValidationError **Location:** `src/hooks.server.js` Called when remote function argument doesn't match [Standard Schema](https://standardschema.dev/). Must return object matching [`App.Error`](types#Error). ```js /// file: todos.remote.js import * as v from 'valibot'; import { query } from '$app/server'; export const getTodo = query(v.string(), (id) => { // implementation... }); ``` ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').HandleValidationError} */ export function handleValidationError({ issues }) { return { message: 'No thank you' }; } ``` Default: 400 status, 'Bad Request' message. Be careful exposing information. ## handleError **Location:** `src/hooks.server.js` and `src/hooks.client.js` Called for [unexpected errors](errors#Unexpected-errors). Allows logging and generating safe error representation. Returned value becomes `page.error`. Default status: 500, message: "Internal Error" Customize `App.Error` interface: ```ts /// file: src/app.d.ts declare global { namespace App { interface Error { message: string; errorId: string; } } } export {}; ``` ```js /// file: src/hooks.server.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js //cut import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleServerError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', errorId }; } ``` ```js /// file: src/hooks.client.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js //cut import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleClientError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', errorId }; } ``` **Note:** Client type is `HandleClientError`, `event` is `NavigationEvent`. Not called for expected errors (using [`error`](@sveltejs-kit#error) function). **Warning:** `handleError` must never throw. ## init **Location:** `src/hooks.server.js` and `src/hooks.client.js` Runs once at server creation or app start. For async initialization (e.g., database connections). ```js // @errors: 2307 /// file: src/hooks.server.js import * as db from '$lib/server/database'; /** @type {import('@sveltejs/kit').ServerInit} */ export async function init() { await db.connect(); } ``` **Note:** Browser async work in `init` delays hydration. ## reroute **Location:** `src/hooks.js` (runs on both server and client) Runs before `handle`. Changes URL-to-route translation. Returned pathname selects route and parameters. ```js // @errors: 2345 2304 /// file: src/hooks.js /** @type {Record} */ const translated = { '/en/about': '/en/about', '/de/ueber-uns': '/de/about', '/fr/a-propos': '/fr/about', }; /** @type {import('@sveltejs/kit').Reroute} */ export function reroute({ url }) { if (url.pathname in translated) { return translated[url.pathname]; } } ``` Doesn't change browser address bar or `event.url`. Can be async (since v2.18): ```js // @errors: 2345 2304 /// file: src/hooks.js /** @type {import('@sveltejs/kit').Reroute} */ export async function reroute({ url, fetch }) { // Ask a special endpoint within your app about the destination if (url.pathname === '/api/reroute') return; const api = new URL('/api/reroute', url); api.searchParams.set('pathname', url.pathname); const result = await fetch(api).then(r => r.json()); return result.pathname; } ``` **Note:** `reroute` is pure/idempotent. Result cached on client per unique URL. ## transport **Location:** `src/hooks.js` (runs on both server and client) Transporters for passing custom types across server/client boundary. Each has `encode` (server) and `decode` (client): ```js // @errors: 2307 /// file: src/hooks.js import { Vector } from '$lib/math'; /** @type {import('@sveltejs/kit').Transport} */ export const transport = { Vector: { encode: (value) => value instanceof Vector && [value.x, value.y], decode: ([x, y]) => new Vector(x, y) } }; ``` ## docs/kit/30-advanced/25-errors.md # Errors SvelteKit distinguishes between expected and unexpected errors, both represented as `{ message: string }` objects by default. ## Expected errors Created with `error()` helper from `@sveltejs/kit`: ```js import { error } from '@sveltejs/kit'; import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { const post = await db.getPost(params.slug); if (!post) { error(404, { message: 'Not found' }); } return { post }; } ``` Sets response status and renders `+error.svelte` where `page.error` is the error object: ```svelte

{page.error.message}

``` Add custom properties: ```js error(404, { message: 'Not found', code: 'NOT_FOUND' }); ``` Or use string shorthand: ```js error(404, 'Not found'); // equivalent to error(404, { message: 'Not found' }) ``` ## Unexpected errors Any other exception during request handling. Default shape: `{ "message": "Internal Error" }`. Logged to console/server logs. Go through `handleError` hook for custom handling (reporting, transforming). ## Rendering errors Enable experimental option (SvelteKit 2.54+, Svelte 5.53+): ```js /// file: svelte.config.js // @errors: 2353 /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { experimental: { handleRenderingErrors: true } } }; export default config; ``` Wraps routes in error boundary. Errors during rendering show nearest `+error.svelte`. Error passed to `handleError` first, then to component as prop (not via `page` object): ```svelte

{error.message}

``` Same for custom boundaries: ```svelte ... {#snippet failed(error: App.Error)} {error.message} {/snippet} ``` ## Responses Errors in `handle` or `+server.js` return fallback HTML or JSON based on `Accept` headers. Custom fallback via `src/error.html`: ```html %sveltekit.error.message%

My custom error page

Status: %sveltekit.status%

Message: %sveltekit.error.message%

``` Errors in `load` render nearest `+error.svelte`. For root layout errors, uses fallback page. ## Type safety Customize error shape via `App.Error` interface: ```ts /// file: src/app.d.ts declare global { namespace App { interface Error { code: string; id: string; } } } export {}; ``` Always includes `message: string`. ## docs/kit/30-advanced/30-link-options.md # Link options SvelteKit uses `` elements for navigation. Customize behavior with `data-sveltekit-*` attributes on links or parent elements. Also applies to `
`. ## data-sveltekit-preload-data Preload page data on hover/touch before click happens. **Values:** - `"hover"` - preload on mouse rest over link (desktop) or `touchstart` (mobile) - `"tap"` - preload on `touchstart` or `mousedown` only **Default in `src/app.html`:** ```html
%sveltekit.body%
``` **Override for specific links:** ```html
Get current stonk values ``` > Ignored if `navigator.connection.saveData` is `true`. ## data-sveltekit-preload-code Preload code without data. **Values (decreasing eagerness):** - `"eager"` - preload immediately - `"viewport"` - preload when entering viewport - `"hover"` - preload on hover (code only) - `"tap"` - preload on tap (code only) > `viewport` and `eager` only work for links present immediately after navigation, not dynamically added links. > Only takes effect if more eager than `data-sveltekit-preload-data`. Ignored if `saveData` is `true`. ## data-sveltekit-reload Force full-page navigation (bypass SvelteKit routing). ```html Path ``` > Links with `rel="external"` get same treatment and are ignored during prerendering. ## data-sveltekit-replacestate Replace history entry instead of creating new one. ```html Path ``` ## data-sveltekit-keepfocus Keep focus on current element after navigation. ```html
``` > Avoid on links. Only use on elements that persist after navigation. ## data-sveltekit-noscroll Prevent scroll reset to 0,0 after navigation. ```html Path ``` ## Disabling options Use `"false"` value to disable: ```html
a b c
d e f
``` **Conditional:** ```svelte
``` ## docs/kit/30-advanced/40-service-workers.md # Service Workers Service workers act as proxy servers handling network requests. Enable offline support and speed up navigation by precaching assets. ## Setup Create `src/service-worker.js` (or `src/service-worker/index.js`) - it will be bundled and auto-registered. ## $service-worker Module Provides: - Paths to static assets, build files, prerendered pages - App version string (for cache naming) - Deployment `base` path - Vite `define` config applied > **Note:** `build` and `prerendered` are empty arrays during development ## Example: Offline-capable Service Worker Caches built app and static files eagerly, other requests on-demand. Makes pages work offline once visited. ```js // @errors: 2688 2307 /// file: src/service-worker.js // Disables access to DOM typings like `HTMLElement` which are not available // inside a service worker and instantiates the correct globals /// /// /// // Ensures that the `$service-worker` import has proper type definitions /// // Only necessary if you have an import from `$env/static/public` /// import { build, files, version } from '$service-worker'; // This gives `self` the correct types const self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self)); // Create a unique cache name for this deployment const CACHE = `cache-${version}`; const ASSETS = [ ...build, // the app itself ...files // everything in `static` ]; self.addEventListener('install', (event) => { // Create a new cache and add all files to it async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); } event.waitUntil(addFilesToCache()); }); self.addEventListener('activate', (event) => { // Remove previous cached data from disk async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } } event.waitUntil(deleteOldCaches()); }); self.addEventListener('fetch', (event) => { // ignore POST requests etc if (event.request.method !== 'GET') return; async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE); // `build`/`files` can always be served from the cache if (ASSETS.includes(url.pathname)) { const response = await cache.match(url.pathname); if (response) { return response; } } // for everything else, try the network first, but // fall back to the cache if we're offline try { const response = await fetch(event.request); // if we're offline, fetch can return a value that is not a Response // instead of throwing - and we can't pass this non-Response to respondWith if (!(response instanceof Response)) { throw new Error('invalid response from fetch'); } if (response.status === 200 && !response.headers.get('cache-control')?.includes('no-store')) { cache.put(event.request, response.clone()); } return response; } catch (err) { const response = await cache.match(event.request); if (response) { return response; } // if there's no cache, then just error out // as there is nothing we can do to respond to this request throw err; } } event.respondWith(respond()); }); ``` > **Warning:** Stale data may be worse than no data. Browsers empty caches when full - avoid caching large assets like videos. ## Manual Registration Disable auto-registration in config if needed. Default registration: ```js import { dev } from '$app/environment'; if ('serviceWorker' in navigator) { addEventListener('load', function () { navigator.serviceWorker.register('./path/to/service-worker.js', { type: dev ? 'module' : 'classic' }); }); } ``` > **Note:** Service worker is bundled for production only, not during development. ## Updating Service Workers Browsers check for updates on full-page navigation or functional events (`push`, `sync`). Client-side navigation doesn't trigger updates. SvelteKit calls `registration.update()` only during error recovery (failed route load + version polling detects redeploy). **Trigger updates manually** (e.g., on every navigation): ```js import { afterNavigate } from '$app/navigation'; afterNavigate(async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration(); await registration?.update(); } }); ``` New service worker installs in background, takes over when existing tabs close. ## Alternatives - [Workbox](https://web.dev/learn/pwa/workbox) - Popular PWA library - [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html) - Workbox integration for SvelteKit ## docs/kit/30-advanced/50-server-only-modules.md # Server-only modules SvelteKit prevents accidental import of sensitive server code into client bundles. ## Private environment variables `$env/static/private` and `$env/dynamic/private` can only be imported in server modules like `hooks.server.js` or `+page.server.js`. ## Server-only utilities `$app/server` module (contains `read()` for filesystem access) only works on server. ## Your modules Make modules server-only by: - Adding `.server` to filename: `secrets.server.js` - Placing in `$lib/server`: `$lib/server/secrets.js` ## How it works ```js /// file: $lib/server/secrets.js export const atlantisCoordinates = [/* redacted */]; ``` ```js /// file: src/routes/utils.js export { atlantisCoordinates } from '$lib/server/secrets.js'; export const add = (a, b) => a + b; ``` ```html /// file: src/routes/+page.svelte ``` **Error:** ``` Cannot import $lib/server/secrets.ts into code that runs in the browser src/routes/+page.svelte imports src/routes/utils.js imports $lib/server/secrets.ts If you're only using the import as a type, change it to `import type`. ``` Even though `+page.svelte` only uses `add`, the entire import chain is blocked because it could leak `atlantisCoordinates`. Works with dynamic imports including interpolated ones: `` await import(`./${foo}.js`) `` > **Note:** Disabled during tests (when `process.env.TEST === 'true'`) ## docs/kit/30-advanced/65-snapshots.md # Snapshots Preserves ephemeral DOM state (scroll positions, input values, etc.) across navigation. ## Usage Export `snapshot` object with `capture` and `restore` methods from `+page.svelte` or `+layout.svelte`: ```svelte