## docs/kit/10-getting-started/10-introduction.md # Introduction ## What is SvelteKit? Framework for building robust, performant web apps using Svelte. Similar to Next (React) or Nuxt (Vue). ## What is Svelte? UI component framework. Compiler converts components to JavaScript (renders HTML) and CSS. See [Svelte tutorial](/tutorial) to learn more. ## 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](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot) via [Vite](https://vitejs.dev/) See [project types](project-types) for application examples. ## 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. Adapter choice and config control build/deploy/render behavior. ## 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](https://www.youtube.com/watch?v=860d8usGC0o). ## Static Site Generation Use [`adapter-static`](adapter-static) for full [prerendering](glossary#Prerendering) ([SSG](glossary#SSG)). Or use [prerender option](page-options#prerender) to prerender some pages and dynamically render others with different adapter. For very large sites: [`adapter-vercel`](adapter-vercel) supports [ISR](adapter-vercel#Incremental-Static-Regeneration). ## Single-Page App [SPAs](glossary#SPA) use [CSR](glossary#CSR) exclusively. See [building SPAs](single-page-apps). If no backend or [separate backend](#Separate-backend), ignore `server` file docs. ## Multi-Page App Not typical for SvelteKit. Options: - [`csr = false`](page-options#csr) removes JS, renders subsequent links on server - [`data-sveltekit-reload`](link-options#data-sveltekit-reload) renders specific links on server ## Separate Backend For backends in Go, Java, PHP, Ruby, Rust, C#, etc: - **Recommended**: Deploy frontend separately using `adapter-node` or serverless adapter - Alternative: Deploy as [SPA](single-page-apps) served by backend (worse SEO/performance) Ignore `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) auto-detects platforms. Or use [`adapter-vercel`](adapter-vercel), [`adapter-netlify`](adapter-netlify), [`adapter-cloudflare`](adapter-cloudflare). [Community adapters](/packages#sveltekit-adapters) support most serverless 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 containers. ## Library Create library for other Svelte apps 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 [SvelteKit 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. Platforms use local web server. Consider [`bundleStrategy: 'single'`](configuration#output) to limit requests (Capacitor uses HTTP/1, limiting concurrent connections). ## Desktop App Turn [SvelteKit 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 microcontrollers/TVs with limited concurrent connections, 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 (served as-is) ├ tests/ ├ package.json ├ svelte.config.js ├ tsconfig.json └ vite.config.js ``` ## Key Files ### src/lib - Import via `$lib` alias - `server/` subdirectory: server-only code via `$lib/server` alias (client imports prevented) ### src/app.html Page template with placeholders: - `%sveltekit.head%` — ``, `

{data.title}

{@html data.content}
``` Also receives `params` prop (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 function. 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. Can also export actions for form submissions. ## +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` hook or `+server.js` handlers. ## +layout ### +layout.svelte Shared UI across pages. Must include `{@render children()}`. ```svelte {@render children()} ``` Layouts nest. Child layouts inherit parent layouts. ```svelte

Settings

{@render children()} ``` ### +layout.js Provides data to layout and child pages: ```js /// file: src/routes/settings/+layout.js /** @type {import('./$types').LayoutLoad} */ export function load() { return { sections: [ { slug: 'profile', title: 'Profile' }, { slug: 'notifications', title: 'Notifications' } ] }; } ``` Can export page options as defaults for children. ### +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)); } ``` **Notes:** - `+layout` files don't affect `+server.js` - Errors return JSON or fallback HTML, not `+error.svelte` ### 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); } ``` ### Fallback method handler Handles unhandled HTTP 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 `+server.js` can coexist with `+page` files: - `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. `PageProps`/`LayoutProps` (2.16.0+) type all props: ```svelte ``` Load function types: `PageLoad`, `PageServerLoad`, `LayoutLoad`, `LayoutServerLoad` IDE tooling can auto-insert types—manual annotation optional. ## Other files Non-route files in route directories are ignored. Colocate components with routes. Shared modules go in `$lib`. ## docs/kit/20-core-concepts/20-load.md # Loading data ## Page data `+page.js` exports a `load` function. Return value available 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.server.js` for server-only `load` (database access, private env vars): ```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()}
``` Child components access parent layout data. Last key wins if multiple `load` functions return same key. ## page.data Access page/child data from parent layouts 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 (from server `load` if both exist) **When to use:** - Server: database access, private credentials, filesystem - Universal: external API without credentials, non-serializable returns - Both: server `load` passes data to universal `load` via `data` property ## Using URL data ### url `URL` instance with `origin`, `hostname`, `pathname`, `searchParams`. `url.hash` unavailable during `load`. ### route Current route directory: `route.id` is `/a/[b]/[...c]` ### params Derived from `url.pathname` and `route.id`: ```json { "b": "x", "c": "y/z" } ``` ## Making fetch requests Provided `fetch` function: - Inherits `cookie`/`authorization` headers on server - Allows relative requests on server - Internal requests go direct to handler (no HTTP overhead) - Response captured and inlined during SSR - Response read from HTML during hydration (no extra request) ```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 is same host or more specific subdomain. ## Headers `setHeaders` sets response headers (server-side 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(); } ``` Cannot set same header multiple times. Use `cookies.set()` for `set-cookie`. ## Using parent data `await parent()` accesses parent `load` data: ```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 operations 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:** - No streaming on platforms without support (AWS Lambda, Firebase) - Can't `setHeaders` or redirect inside streamed promise - Don't return promises from universal `load` if SSR enabled - Headers/status can't change after streaming starts ## Parallel loading All `load` functions run concurrently. Server `load` results grouped into single response. ## Rerunning load functions `load` reruns when dependencies change: - Referenced `params` property changes - Referenced `url` property changes (`pathname`, `search`) - `url.searchParams.get/getAll/has()` parameter changes - `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}

``` ## Auth implications - Layout `load` doesn't run on every request (e.g., client-side nav) - Layout and page `load` run concurrently unless `await parent()` called - If layout `load` throws, page `load` runs but client won't receive data **Strategies:** - Use hooks to protect routes before `load` runs - Put auth guards in `+page.server.js` for route-specific protection - Avoid auth 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(307, `/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 export const actions = { default: async (event) => { // TODO log the user in } }; ``` ```svelte
``` **From other pages:** Add `action` attribute pointing to the page: ```html
``` > Actions always use `POST` requests. ## Named Actions ```js /// file: src/routes/login/+page.server.js export const actions = { login: async (event) => { // TODO log the user in }, register: async (event) => { // TODO register the user } }; ``` **Invoke with query parameter:** ```svelte
``` **Or from other pages:** ```svelte ``` **Use `formaction` on buttons:** ```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'; export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } 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'; 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'; 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. **Important:** `handle` runs before action and doesn't rerun before `load`. Update `event.locals` in action if needed: ```js /// file: src/hooks.server.js 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 export function load(event) { return { user: event.locals.user }; } 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"` and actions in `+page.server.js`. **Default behavior:** - Updates `form`, `page.form`, `page.status` (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 }; }} > ``` **Using `applyAction`:** ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); } }; }} > ``` **`applyAction(result)` behavior:** - `success`, `failure` — sets `page.status`, updates `form` and `page.form` - `redirect` — calls `goto(result.location, { invalidateAll: true })` - `error` — renders nearest `+error` boundary ### Custom Event Listener ```svelte
``` **Must use `deserialize()` not `JSON.parse()`** - supports `Date` and `BigInt`. **POST to action with `+server.js` present:** ```js const response = await fetch(this.action, { method: 'POST', body: data, headers: { 'x-sveltekit-action': 'true' } }); ``` ## Alternatives Use `+server.js` for JSON APIs: ```svelte ``` ```js /// file: src/routes/api/ci/+server.js 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
``` Navigates to `/search?q=...`, invokes `load` but not action. Supports `data-sveltekit-*` attributes like `
` elements. ## docs/kit/20-core-concepts/40-page-options.md # Page Options SvelteKit renders components server-side first, 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 to find pages. 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" means crawler didn't reach the route. **Fixes:** - Add links from `config.kit.prerender.entries` or [`entries`](#entries) - Ensure links exist on prerendered pages - Change to `export const prerender = 'auto'` ## entries Define dynamic route parameters for prerendering: ```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 instead of full HTML. Not recommended for most cases. **Note:** If all page options are static values, SvelteKit evaluates them statically. Otherwise, it imports files at build/runtime, so browser-only code must not run on module load. ## 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 must work with HTML/CSS only - All ` ``` ```svelte

Welcome {user().name}

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

{data.title}

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

{@html data.content}
``` ✅ Use reactive state: ```svelte /// file: src/routes/blog/[slug]/+page.svelte ``` > Use [afterNavigate]($app-navigation#afterNavigate) / [beforeNavigate]($app-navigation#beforeNavigate) if you need to re-run `onMount`/`onDestroy` logic. Force remount with `{#key}`: ```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` - Access in `load` via `url` parameter - Access in components via `page.url.searchParams` - Set via `
`, `
`, or `goto('?key=value')` ## 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** - Enable in `svelte.config.js`: ```js /// file: svelte.config.js const config = { kit: { experimental: { remoteFunctions: true } }, compilerOptions: { experimental: { async: true // for await in components } } }; ``` Remote functions are type-safe client-server communication. They're called anywhere but always run on the server, allowing safe access to server-only modules (env vars, DB clients). Export from `.remote.js` or `.remote.ts` files. Four types: `query`, `form`, `command`, `prerender`. ## query Read dynamic data from server: ```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; }); ``` Use with `await` in components: ```svelte ``` Alternative: 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 and return values serialized with [devalue](https://github.com/sveltejs/devalue). ### Refreshing queries ```svelte ``` Queries are cached: `getPosts() === getPosts()`. ## 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); }); ``` ## form Write data to server with progressive enhancement: ```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 and reloads). With JS, progressively enhanced (no reload). ### Fields Field types: strings, numbers, booleans, `File` objects. Can be nested in objects/arrays. ```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
Name Photo Height (cm) I like dogs
``` **Note:** Unchecked checkboxes not in FormData, use `v.optional(v.boolean(), false)`. **Note:** Generated `name` uses JS object notation. Boolean/number fields prefixed with `b:`/`n:`. Radio/checkbox groups need value as second arg: ```svelte {#each operatingSystems as os} {/each} ``` Select elements: ```svelte ``` ### Programmatic validation Use `invalid` helper for runtime validation: ```js 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()}> ``` Validate all inputs: `validate({ includeUntouched: true })`. Client-side preflight validation: ```svelte ``` All issues: `createPost.fields.allIssues()`. ### Getting/setting inputs Get current value: ```svelte

{createPost.fields.title.value()}

``` Set values: ```svelte ``` ### Handling sensitive data Prefix with underscore to prevent sending back to user: ```svelte ``` ### Single-flight mutations Refresh specific queries instead of all. Server-side: ```js export const createPost = form( v.object({/* ... */}), async (data) => { // form logic... await getPosts().refresh(); redirect(303, `/blog/${slug}`); } ); export const updatePost = form( v.object({/* ... */}), async (data) => { const result = externalApi.update(post); await getPost(post.id).set(result); } ); ``` ### 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} ``` Result is ephemeral (vanishes on resubmit/navigate/reload). ### enhance Customize submission behavior: ```svelte { try { await submit(); form.reset(); showToast('Successfully published!'); } catch (error) { showToast('Oh no! Something went wrong'); } })}> ``` Client-driven single-flight mutations: ```ts await submit().updates(getPosts()); ``` Optimistic updates: ```ts await submit().updates( getPosts().withOverride((posts) => [newPost, ...posts]) ); ``` ### Multiple instances Isolate repeated forms: ```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') { // handle login } else { // handle registration } } ); ``` ## command Write data from anywhere (not element-specific): ```js /// file: likes.remote.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

likes: {await getLikes(item.id)}

``` **Note:** Commands cannot be called during render. Prefer `form` where possible. ### Updating queries Inside command: ```js export const addLike = command(v.string(), async (id) => { await db.sql`UPDATE item SET likes = likes + 1 WHERE id = ${id}`; getLikes(id).refresh(); // or: getLikes(id).set(...) }); ``` When calling: ```ts await addLike(item.id).updates(getLikes(item.id)); ``` Optimistic updates: ```ts await addLike(item.id).updates( getLikes(item.id).withOverride((n) => n + 1) ); ``` ## prerender Invoked at build time for static data: ```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; }); ``` Data cached using Cache API, survives reloads, cleared on new deployment. ### Prerender arguments ```js export const getPost = prerender(v.string(), async (slug) => { const [post] = await db.sql`SELECT * FROM post WHERE slug = ${slug}`; if (!post) error(404, 'Not found'); return post; }); ``` Specify inputs: ```js export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { inputs: () => ['first-post', 'second-post', 'third-post'] } ); ``` Allow dynamic calls: ```js export const getPost = prerender( v.string(), async (slug) => { /* ... */ }, { dynamic: true, inputs: () => ['first-post', 'second-post', 'third-post'] } ); ``` ## Handling validation errors Implement `handleValidationError` hook: ```js /// file: src/hooks.server.ts export function handleValidationError({ event, issues }) { return { message: 'Nice try, hacker!' }; } ``` Opt out of validation: ```ts export const getStuff = query('unchecked', async ({ id }: { id: string }) => { // shape might not match TypeScript }); ``` ## Using `getRequestEvent` Access `RequestEvent` inside `query`, `form`, `command`: ```ts import { getRequestEvent, query } from '$app/server'; export const getProfile = query(async () => { const user = await getUser(); return { name: user.name, avatar: user.avatar }; }); const getUser = query(async () => { const { cookies } = getRequestEvent(); return await findUser(cookies.get('session_id')); }); ``` **Note:** Some properties differ in remote functions: - Cannot set headers (except cookies in `form`/`command`) - `route`, `params`, `url` relate to calling page, not endpoint - Don't use for authorization checks ## Redirects Use `redirect(...)` in `query`, `form`, `prerender`. **Not** in `command` (return `{ redirect: location }` if needed). ## docs/kit/25-build-and-deploy/10-building-your-app.md # Building your app Building happens in two stages when running `vite build`: 1. Vite creates optimized production builds (server code, browser code, service worker). Prerendering executes here. 2. An adapter tunes the build for your 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, so 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 `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 /// 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 /** * @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 (highest to lowest): 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 for `/foo-abc`: ```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` Get hex code: `':'.charCodeAt(0).toString(16); // '3a'` Unicode escapes `[u+nnnn]` also supported: ``` src/routes/[u+d83e][u+dd2a]/+page.svelte src/routes/🤪/+page.svelte // equivalent ``` > Encode `.well-known` as `[x+2e]well-known` for TypeScript compatibility ## Advanced Layouts ### (group) Parentheses create groups without affecting URLs: ```tree src/routes/ │ (app)/ │ ├ dashboard/ │ ├ item/ │ └ +layout.svelte │ (marketing)/ │ ├ about/ │ ├ testimonials/ │ └ +layout.svelte ├ admin/ └ +layout.svelte ``` `+page` can go directly in `(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 Not always necessary. Alternative: reuse components/functions: ```svelte {@render children()} ``` ```js /// file: src/routes/nested/route/+layout.js 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` — universal hooks (both client and server) Code runs at app startup, useful for initializing database clients. > Configure location with [`config.kit.files.hooks`](configuration#files) ## Server hooks ### handle Runs on every server [request](web-standards#Fetch-APIs-Request) (including [prerendering](page-options#prerender)). Receives `event` and `resolve` function. Modify response 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; } ``` > Static assets and prerendered pages are _not_ handled by SvelteKit. Default: `({ event, resolve }) => resolve(event)` Check [`building`]($app-environment#building) to exclude code from prerendering. #### 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; } ``` Chain multiple `handle` functions with [`sequence`](@sveltejs-kit-hooks). #### 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`). Not called in dev mode. ```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; } ``` `resolve(...)` never throws — always returns `Promise`. Errors elsewhere in `handle` are fatal. Customize error page via `src/error.html`. See [error handling](errors). ### handleFetch Modify [`event.fetch`](load#Making-fetch-requests) calls on server/during prerendering (in endpoints, `load`, `action`, `handle`, `handleError`, `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); } ``` `event.fetch` follows browser credentials model. Same-origin: forwards `cookie`/`authorization` unless `credentials: "omit"`. Cross-origin: includes `cookie` 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 Called when remote function argument doesn't match [Standard Schema](https://standardschema.dev/). Must return [`App.Error`](types#Error) shape. ```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 — likely malicious requests. ## Shared hooks Add to both `src/hooks.server.js` _and_ `src/hooks.client.js`: ### handleError Called on [unexpected errors](errors#Unexpected-errors) during loading/rendering/endpoints. Receives `error`, `event`, `status`, `message`. Use to: - Log errors - Generate safe error representation (becomes `$page.error`) Default return: `{ message }`. Status is 500, message is "Internal Error" for code errors. Customize via `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 }; } ``` > Client: type is `HandleClientError`, `event` is `NavigationEvent` not `RequestEvent`. Not called for expected errors ([`error`](@sveltejs-kit#error) function). Dev: Svelte syntax errors include `frame` property. > `handleError` must never throw. ### init Runs once at server creation or browser app start. For async work like 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(); } ``` > Browser: async work in `init` delays hydration. ## Universal hooks Add to `src/hooks.js`. Runs on both server and client. ### reroute 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`. Since 2.18: can be async. Use `fetch` argument (same benefits as `load` fetch, but `params`/`id` unavailable to `handleFetch`). ```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; } ``` > `reroute` is pure/idempotent. SvelteKit caches results on client (called once per unique URL). ### transport Transporters for passing custom types across server/client boundary (from `load`/form actions). 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 /// file: src/routes/blog/[slug]/+page.server.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 nearest `+error.svelte` component 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" }`. Sensitive info not exposed to users. Use [`handleError`](hooks#Shared-hooks-handleError) hook for custom handling (logging, reporting, etc.). ## Responses **In `handle` or `+server.js`:** Returns fallback error page or JSON based on `Accept` headers. **In `load` functions:** Renders nearest `+error.svelte` component. If error in root `+layout(.server).js`, uses fallback error page. ### Custom Fallback Error Page ```html %sveltekit.error.message%

My custom error page

Status: %sveltekit.status%

Message: %sveltekit.error.message%

``` ## 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` property. ## 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 before click by detecting hover/touch events. **Values:** - `"hover"` - preload on mouse hover (desktop) or `touchstart` (mobile) - `"tap"` - preload on `touchstart` or `mousedown` only **Default template:** ```html
%sveltekit.body%
``` **Example:** ```html
Get current stonk values ``` > Ignored if `navigator.connection.saveData` is `true`. ## data-sveltekit-preload-code Preload route code without data. **Values (decreasing eagerness):** - `"eager"` - preload immediately - `"viewport"` - preload when link enters viewport - `"hover"` - preload on hover (code only) - `"tap"` - preload on tap (code only) > `viewport` and `eager` only apply to links present immediately after navigation. > Only effective if more eager than `data-sveltekit-preload-data`. > Ignored if user has reduced data usage. ## data-sveltekit-reload Force full-page navigation (browser handles link). ```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 Prevent focus reset after navigation. ```html
``` > Avoid on links. Only use on elements that persist after navigation. ## data-sveltekit-noscroll Prevent scroll to top (0,0) after navigation. ```html Path ``` ## Disabling options Use `"false"` to disable inherited attributes: ```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 built JS/CSS. ## Setup Place service worker at `src/service-worker.js` (or `src/service-worker/index.js`) - auto-bundled and registered. Can [disable auto-registration](configuration#serviceWorker) and register manually: ```js if ('serviceWorker' in navigator) { addEventListener('load', function () { navigator.serviceWorker.register('./path/to/service-worker.js'); }); } ``` ## `$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 ## Example: Offline-Capable Caching Eagerly caches built app + `static` files. Caches other requests on-demand. Makes pages work offline once visited. ```js /// file: src/service-worker.js /// /// /// /// /// import { build, files, version } from '$service-worker'; const self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self)); const CACHE = `cache-${version}`; const ASSETS = [ ...build, // the app itself ...files // everything in `static` ]; self.addEventListener('install', (event) => { async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); } event.waitUntil(addFilesToCache()); }); self.addEventListener('activate', (event) => { async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } } event.waitUntil(deleteOldCaches()); }); self.addEventListener('fetch', (event) => { 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 (!(response instanceof Response)) { throw new Error('invalid response from fetch'); } if (response.status === 200) { cache.put(event.request, response.clone()); } return response; } catch (err) { const response = await cache.match(event.request); if (response) { return response; } throw err; } } event.respondWith(respond()); }); ``` **Gotchas:** - Stale data may be worse than no data - Browsers empty caches when full - avoid caching large assets (videos) ## Development Service worker bundled for production only. Dev requires browsers supporting [modules in service workers](https://web.dev/es-modules-in-sw). Manual registration in dev needs `{ type: 'module' }`: ```js import { dev } from '$app/environment'; navigator.serviceWorker.register('/service-worker.js', { type: dev ? 'module' : 'classic' }); ``` **Note:** `build` and `prerendered` are empty arrays in dev. ## Alternatives - [Workbox](https://web.dev/learn/pwa/workbox) - industry standard 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-side 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 Importing server-only code in public-facing code causes build error: ```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 only `add` is used, the entire import chain is blocked to prevent leaking `atlantisCoordinates`. Works with dynamic imports including interpolated ones: `` await import(`./${foo}.js`) `` > **Note:** Detection disabled when `process.env.TEST === 'true'` (for Vitest, etc.) ## 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