SvelteKit
A concept-first tour of SvelteKit — routing, load functions, server vs client code, and form actions — building on Svelte.
SvelteKit is the official Svelte framework: it takes Svelte's component model and adds the pieces a real app needs — file-based routing, server rendering, data loading, and a production build — with sensible defaults. This guide assumes you know the Svelte basics; if not, start with the Svelte guide. Examples target SvelteKit 2 with Svelte 5.
SvelteKit 2 defaults: file-based routing under src/routes/, Vite as the bundler, server-side
rendering on by default, and Svelte 5 with runes. New projects scaffold with TypeScript and
Tailwind out of the box.
Creating a project
The Svelte CLI scaffolds a project with everything wired up — TypeScript, routing, and a dev server.
npx sv create my-app
cd my-app
npm run dev # Vite dev server on http://localhost:5173File-based routing
Routes are folders under src/routes/, not configuration. Each folder becomes a URL segment,
and a +page.svelte inside it makes that segment a routable page. A +layout.svelte wraps every
page beneath it with shared UI (nav, footer) that persists across navigation; it renders its
children through a {@render children()} snippet.
<script lang="ts">
let { children } = $props();
</script>
<!-- Wraps all routes; persists across navigations -->
<nav>My site</nav>
{@render children()}<!-- Becomes the /about route -->
<h1>About us</h1>Server vs client code
The core mental shift is where code runs. Files marked with .server run only on the server —
they can talk to your database or read secrets, and their code never ships to the browser. A
+page.server.ts is the safe place for privileged work; a plain +page.svelte is the interactive
UI that runs in the browser.
// Runs only on the server — safe for secrets and direct DB access
export function load() {
return { message: "Rendered on the server." };
}<script lang="ts">
let { data } = $props(); // data comes from the load function above
</script>
<p>{data.message}</p>Dynamic routes
A folder named in square brackets captures part of the URL as a parameter. A load function
receives it via params, and the page reads the result through its data prop.
export function load({ params }: { params: { slug: string } }) {
return { slug: params.slug };
}<script lang="ts">
let { data } = $props();
</script>
<h1>Post: {data.slug}</h1>Loading data
Data fetching lives in a load function, not in the component. A load in +page.ts runs on
both server and client; a load in +page.server.ts runs only on the server. Whatever it returns
becomes the page's data prop — no $effect, no loading-state plumbing.
export async function load({ fetch }) {
const res = await fetch("https://example.com/penguins.json");
const penguins: { id: number; species: string }[] = await res.json();
return { penguins };
}<script lang="ts">
let { data } = $props();
</script>
<ul>
{#each data.penguins as p (p.id)}
<li>{p.species}</li>
{/each}
</ul>SvelteKit gives load an enhanced fetch that, among other things, lets server-side requests run
in-process and forwards cookies — so the same code works on both sides.
Mutating data with form actions
A form action is an async function exported from a +page.server.ts that handles a form
submission on the server. The form posts to it directly, so you get working mutations even before
any JavaScript loads — and use:enhance upgrades it to a smooth client-side submit when it does.
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const title = data.get("title");
// ...write to your database here
},
};<script lang="ts">
import { enhance } from "$app/forms";
</script>
<form method="POST" use:enhance>
<input name="title" />
<button type="submit">Create</button>
</form>Linking and navigation
Use ordinary <a> tags. SvelteKit's router intercepts clicks on internal links, does client-side
transitions instead of full reloads, and prefetches the target in the background so navigation
feels instant.
<a href="/about">About</a>You can tune prefetching per link with data-sveltekit-preload-data if you need finer control.
Page metadata
Set per-page <head> tags — titles, descriptions, Open Graph — with the <svelte:head> element.
For dynamic values, combine it with data from your load function for SEO and sharing.
<svelte:head>
<title>About us</title>
<meta name="description" content="Who we are." />
</svelte:head>
<h1>About us</h1>SvelteKit 2 details worth knowing
A few conventions catch newcomers out: data loading belongs in load functions, not in component
$effects; $app/stores is deprecated in favor of the reactive page, navigating, and
updated objects from $app/state (which also work inside .svelte.ts files); and the
.server.ts suffix is what guarantees code stays off the client. When in doubt about where code
runs, check the filename.