## 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%` — ``, `{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() }; } ``` ```svelterandom 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 ` ``` 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 ``` > 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 ``` ### 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 ``` 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 - ` ``` ```svelteWelcome {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. ```svelteReading time: {Math.round(estimatedReadingTime)} minutes
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 ` ``` > 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 ``` **Conditional:** ```svelte