## 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 `
```
### 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 `
```
## 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
```
- **Progressive enhancement**: Works without JS via `method`/`action`; with JS it submits without full reload.
- **Single-flight mutations**:
- Server-driven: call refresh inside the handler:
```js
await getPosts().refresh();
```
- Client-driven: customize with `enhance` and `submit().updates(...)`:
```svelte