## Project types SvelteKit supports all rendering modes: SPA, SSR, SSG, and you can mix them within one project. ## Setup Scaffold a new SvelteKit project using `npx sv create` then follow the instructions. Do NOT use `npm create svelte` anymore, this command is deprecated. A SvelteKit project needs a `package.json` with the following contents at minimum: ```json { "devDependencies": { "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } } ``` Do NOT put any of the `devDependencies` listed above into `dependencies`, keep them all in `devDependencies`. It also needs a `vite.config.js` with the following at minimum: ```js import { defineConfig } from 'vite'; import { sveltekit } from '@sveltejs/kit/vite'; export default defineConfig({ plugins: [sveltekit()] }); ``` It also needs a `svelte.config.js` with the following at minimum: ```js import adapter from '@sveltejs/adapter-auto'; export default { kit: { adapter: adapter() } }; ``` ## Project structure - **`src/` directory:** - `lib/` for shared code (`$lib`), `lib/server/` for server‑only modules (`$lib/server`), `params/` for matchers, `routes/` for your pages/components, plus `app.html`, `error.html`, `hooks.client.js`, `hooks.server.js`, and `service-worker.js`. - Do **NOT** import server‑only code into client files - **Top‑level assets & configs:** - `static/` for public assets; `tests/` (if using Playwright); config files: `package.json` (with `@sveltejs/kit`, `svelte`, `vite` as devDeps), `svelte.config.js`, `tsconfig.json` (or `jsconfig.json`, extending `.svelte-kit/tsconfig.json`), and `vite.config.js`. - Do **NOT** forget `"type": "module"` in `package.json` if using ESM. - **Build artifacts:** - `.svelte-kit/` is auto‑generated and safe to ignore or delete; it will be recreated on `dev`/`build`. - Do **NOT** commit `.svelte-kit/` to version control. ## Routing - **Filesystem router:** `src/routes` maps directories to URL paths: Everything with a `+page.svelte` file inside it becomes a visitable URL, e.g. `src/routes/hello/+page.svelte` becomes `/hello`. `[param]` folders define dynamic segments. Do NOT use other file system router conventions, e.g. `src/routes/hello.svelte` does NOT become available als URL `/hello` - **Route files:** Prefix with `+`: all run server‑side; only non‑`+server` run client‑side; `+layout`/`+error` apply recursively. - **Best practice:** Do **not** hard‑code routes in code; instead rely on the filesystem convention. ### +page.svelte - Defines UI for a route, SSR on first load and CSR thereafter - Do **not** fetch data inside the component; instead use a `+page.js` or `+page.server.js` `load` function; access its return value through `data` prop via `let { data } = $props()` (typed with `PageProps`). ```svelte

{data.title}

``` ### +page.js - Load data for pages via `export function load({ params })` (typed `PageLoad`), return value is put into `data` prop in component - Can export `prerender`, `ssr`, and `csr` consts here to influence how page is rendered. - Do **not** include private logic (DB or env vars), can **not** export `actions` from here; if needed, use `+page.server.js`. ```js import type { PageLoad } from './$types'; export const load: PageLoad = () => { return { title: 'Hello world!', }; } ``` ### +page.server.js - `export async function load(...)` (typed `PageServerLoad`) to access databases or private env; return serializable data. - Can also export `actions` for `
` handling on the server. ### +error.svelte - Add `+error.svelte` in a route folder to render an error page, can use `page.status` and `page.error.message` from `$app/state`. - SvelteKit walks up routes to find the closest boundary; falls back to `src/error.html` if none. ### +layout.svelte - Place persistent elements (nav, footer) and include `{@render children()}` to render page content. Example: ```svelte

Some Content that is shared for all pages below this layout

{@render children()} ``` - Create subdirectory `+layout.svelte` to scope UI to nested routes, inheriting parent layouts. - Use layouts to avoid repeating common markup; do **not** duplicate UI in every `+page.svelte`. ### +layout.js / +layout.server.js - In `+layout.js` or `+layout.server.js` export `load()` (typed `LayoutLoad`) to supply `data` to the layout and its children; set `prerender`, `ssr`, `csr`. - Use `+layout.server.js` (typed `LayoutServerLoad`) for server-only things like DB or env access. - Do **not** perform server‑only operations in `+layout.js`; use the server variant. ```js import type { LayoutLoad } from './$types'; export const load: LayoutLoad = () => { return { sections: [ { slug: 'profile', title: 'Profile' }, { slug: 'notifications', title: 'Notifications' } ] }; } ``` ### +server.js (Endpoints) - Export HTTP handlers (`GET`, `POST`, etc.) in `+server.js` under `src/routes`; receive `RequestEvent`, return `Response` or use `json()`, `error()`, `redirect()` (exported from `@sveltejs/kit`). - export `fallback` to catch all other methods. ```js import type { RequestHandler } from './$types'; export const GET: RequestHandler = ({ url }) => { return new Response('hello world'); } ``` ### $types - SvelteKit creates `$types.d.ts` with `PageProps`, `LayoutProps`, `RequestHandler`, `PageLoad`, etc., for type‑safe props and loaders. - Use them inside `+page.svelte`/`+page.server.js`/`+page.js`/`+layout.svelte`/`+layout.server.js`/`+layout.js` by importing from `./$types` ### Other files - Any non‑`+` files in route folders are ignored by the router, use this to your advantage to colocate utilities or components. - For cross‑route imports, place modules under `src/lib` and import via `$lib`. ## Loading data ### Page data - `+page.js` exports a `load` (`PageLoad`) whose returned object is available in `+page.svelte` via `let { data } = $props()` (e.g. when you do `return { foo }` from `load` it is available within `let { data } = $props()` in `+page.svelte` as `data.foo`) - Universal loads run on SSR and CSR; private or DB‑backed loads belong in `+page.server.js` (`PageServerLoad`) and must return devalue‑serializable data. Example: ```js // file: src/routes/foo/+page.js export async function load({ fetch }) { const result = await fetch('/data/from/somewhere').then((r) => r.json()); return { result }; // return property "result" } ``` ```svelte {data.result} ``` ### Layout data - `+layout.js` or `+layout.server.js` exports a `load` (`LayoutLoad`/`LayoutServerLoad`) - Layout data flows downward: child layouts and pages see parent data in their `data` prop. - Data loading flow (interaction of load function and props) works the same as for `+page(.server).js/svelte` ### page.data - The `page` object from `$app/state` gives access to all data from `load` functions via `page.data`, usable in any layout or page. - Ideal for things like `{page.data.title}`. - Types come from `App.PageData` - earlier Svelte versions used `$app/stores` for the same concepts, do NOT use `$app/stores` anymore unless prompted to do so ### Universal vs. server loads - Universal (`+*.js`) run on server first, then in browser; server (`+*.server.js`) always run server‑side and can use secrets, cookies, DB, etc. - Both receive `params`, `route`, `url`, `fetch`, `setHeaders`, `parent`, `depends`; server loads additionally get `cookies`, `locals`, `platform`, `request`. - Use server loads for private data or non‑serializable items; universal loads for public APIs or returning complex values (like constructors). ### Load function arguments - `url` is a `URL` object (no `hash` server‑side); `route.id` shows the route pattern; `params` map path segments to values. - Query parameters via `url.searchParams` trigger reruns when they change. - Use these to branch logic and fetch appropriate data in `load`. ## Making Fetch Requests Use the provided `fetch` function for enhanced features: ```js // src/routes/items/[id]/+page.js export async function load({ fetch, params }) { const res = await fetch(`/api/items/${params.id}`); const item = await res.json(); return { item }; } ``` ## Headers and Cookies Set response headers using `setHeaders`: ```js export async function load({ fetch, setHeaders }) { const response = await fetch(url); setHeaders({ age: response.headers.get('age'), 'cache-control': response.headers.get('cache-control') }); return response.json(); } ``` Access cookies in server load functions using `cookies`: ```js export async function load({ cookies }) { const sessionid = cookies.get('sessionid'); return { user: await db.getUser(sessionid) }; } ``` Do not set `set-cookie` via `setHeaders`; use `cookies.set()` instead. ## Using Parent Data Access data from parent load functions: ```js export async function load({ parent }) { const { a } = await parent(); return { b: a + 1 }; } ``` ## Errors and Redirects Redirect users using `redirect`: ```js import { redirect } from '@sveltejs/kit'; export function load({ locals }) { if (!locals.user) { redirect(307, '/login'); } } ``` Throw expected errors using `error`: ```js import { error } from '@sveltejs/kit'; export function load({ locals }) { if (!locals.user) { error(401, 'not logged in'); } } ``` Unexpected exceptions trigger `handleError` hook and a 500 response. ## Streaming with Promises Server load functions can stream promises as they resolve: ```js export async function load({ params }) { return { comments: loadComments(params.slug), 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} ``` ## Rerunning Load Functions Load functions rerun when: - Referenced params or URL properties change - A parent load function reran and `await parent()` was called - A dependency was invalidated with `invalidate(url)` or `invalidateAll()` Manually invalidate load functions: ```js // In load function export async function load({ fetch, depends }) { depends('app:random'); // ... } // In component import { invalidate } from '$app/navigation'; function rerunLoadFunction() { invalidate('app:random'); } ``` ## Dependency Tracking Exclude from dependency tracking with `untrack`: ```js export async function load({ untrack, url }) { if (untrack(() => url.pathname === '/')) { return { message: 'Welcome!' }; } } ``` ### Implications for authentication - Layout loads don’t automatically rerun on CSR; guards in `+layout.server.js` require child pages to await the parent. - To avoid missed auth checks and waterfalls, use hooks like `handle` for global protection or per‑page server loads. ### Using getRequestEvent - `getRequestEvent()` retrieves the current server `RequestEvent`, letting shared functions (e.g. `requireLogin()`) access `locals`, `url`, etc., without parameter passing. ## Using forms ### Form actions - A `+page.server.js` can export `export const actions: Actions = { default: async (event) => {…} }`; `` in `+page.svelte` posts to the default action without any JS. `+page.js` or `+layout.js` or `+layout.server.js` can NOT export `actions` - Name multiple actions (`login`, `register`) in `actions`, invoke with `action="?/register"` or `button formaction="?/register"`; do NOT use `default` name in this case. - Each action gets `{ request, cookies, params }`, uses `await request.formData()`, sets cookies or DB state, and returns an object that appears on the page as `form` (typed via `PageProps`). Example: Define a default action in `+page.server.js`: ```js // file: src/routes/login/+page.server.js import type { Actions } from './$types'; export const actions: Actions = { default: async (event) => { // TODO log the user in } }; ``` Use it with a simple form: ```svelte
``` ### Validation errors - Return `fail(400, { field, error: true })` from an action to send back status and data; display via `form?.field` and repopulate inputs with `value={form?.field ?? ''}`. - Use `fail` instead of throwing so the nearest `+error.svelte` isn’t invoked and the user can correct their input. - `fail` payload must be JSON‑serializable. ### Redirects - In an action, call `redirect(status, location)` to send a 3xx redirect; this throws and bypasses form re-render. - Client-side, use `goto()` from `$app/navigation` for programmatic redirects. ### Loading data after actions - After an action completes (unless redirected), SvelteKit reruns `load` functions and re‑renders the page, merging the action’s return value into `form`. - The `handle` hook runs once before the action; if you modify cookies in your action, you must also update `event.locals` there to keep `load` in sync. - Do NOT assume `locals` persists automatically; set `event.locals` inside your action when auth state changes. ### Progressive enhancement - Apply `use:enhance` from `$app/forms` to `
` to intercept submissions, prevent full reloads, update `form`, `page.form`, `page.status`, reset the form, invalidate all data, handle redirects, render errors, and restore focus. Do NOT use onsubmit event for progressive enhancement - To customize, provide a callback that runs before submit and returns a handler; use `update()` for default logic or `applyAction(result)` to apply form data without full invalidation. - You can also write your own `onsubmit` listener using `fetch`, then `deserialize` the response and `applyAction`/`invalidateAll`; do NOT use `JSON.parse` for action responses. ```svelte
``` ## Remote functions (experimental) - **What they are**: Type-safe server-only functions you call from the client. They always execute on the server, so they can access server-only modules (env, DB). - If you choose to use them you can replace load functions and form actions with them. - Works best in combination with asynchronous Svelte, i.e. using `await` expressions in `$derived` and template - **Opt-in**: Enable in `svelte.config.js`: ```js export default { kit: { experimental: { remoteFunctions: true } } }; ``` - **Where and how**: - Place `.remote.js`/`.remote.ts` files in `src/lib` or `src/routes`. - Export functions using one of: `query`, `form`, `command`, `prerender` from `$app/server`. - Client imports become fetch-wrappers to generated HTTP endpoints. - Arguments/returns are serialized with devalue (supports Date, Map, custom transport). ### query: read dynamic data Define: ```js // src/routes/blog/data.remote.js import { query } from '$app/server'; import * as db from '$lib/server/database'; export const getPosts = query(async () => { return db.posts(); }); ``` Use in component (recommended with await): ```svelte ``` - **Args + validation**: Pass a Standard Schema (e.g. Valibot/Zod) as first param. ```js import * as v from 'valibot'; export const getPost = query(v.string(), async (slug) => { /* ... */ }); ``` - **Refresh/caching**: Calls are cached on page (`getPosts() === getPosts()`). Refresh via: ```svelte ``` - Alternative props exist (`loading`, `error`, `current`) if you don’t use `await`. ### form: mutation via forms Define: ```js import { form } from '$app/server'; import * as db from '$lib/server/database'; import * as auth from '$lib/server/auth'; import { error, redirect } from '@sveltejs/kit'; export const createPost = form(async (data) => { const user = await auth.getUser(); if (!user) error(401, 'Unauthorized'); const title = data.get('title'); const content = data.get('content'); db.insertPost(title, content); redirect(303, `/blog/${title}`); }); ``` Use: ```svelte