What is SvelteKit?
SvelteKit is the official application framework for Svelte. Svelte gives you components and reactivity; SvelteKit adds routing, server-side data loading, form handling, API endpoints and deployment adapters. It is the recommended way to build anything larger than a widget, and it pairs naturally with Svelte 5.
If you have used Next.js or Nuxt, the shape will feel familiar. The difference is a strong bias toward web standards: forms, requests and responses are platform primitives, and SvelteKit enhances them rather than replacing them.
File-based routing
Everything lives in src/routes. Folders become URL segments, and specially named files define behaviour.
src/routes/
├── +layout.svelte # shared shell for all routes
├── +page.svelte # /
├── about/+page.svelte # /about
├── blog/
│ ├── +page.svelte # /blog
│ └── [slug]/
│ ├── +page.svelte # /blog/:slug
│ └── +page.server.js # data for that page
└── api/
└── posts/+server.js # GET/POST /api/posts
The + prefix marks SvelteKit’s special files. +page.svelte renders a page, +layout.svelte wraps child routes, +page.server.js provides server-only data, and +server.js defines an API endpoint.
Load functions
Load functions fetch data before a page renders. They can run on the server, in the browser, or both.
// src/routes/posts/+page.server.js
export async function load({ fetch }) {
const res = await fetch("/api/posts");
if (!res.ok) throw error(500, "Failed to load posts");
return { posts: await res.json() };
}
<!-- src/routes/posts/+page.svelte -->
<script>
let { data } = $props();
</script>
<ul>
{#each data.posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>
Because the data is resolved before render, the first paint already has content. Use +page.server.js when the code needs secrets or a database, and +page.js when it can run in both places. Layouts can also have load functions, and child loads receive the parent’s data.
Form actions
Forms are first-class. A form action runs on the server and handles the submission, with no client-side fetch required.
// src/routes/posts/new/+page.server.js
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const title = String(data.get("title") ?? "").trim();
if (!title) {
return { success: false, error: "Title is required" };
}
await db.post.create({ data: { title } });
return { success: true };
},
};
<!-- src/routes/posts/new/+page.svelte -->
<script>
let { form } = $props();
</script>
<form method="POST">
<input name="title" />
{#if form?.error}<p class="error">{form.error}</p>{/if}
<button>Create</button>
</form>
The form works before JavaScript loads, and SvelteKit enhances it once the client is ready. The returned value is available in the page’s form prop, so validation feedback is simple.
API endpoints
For JSON APIs, a +server.js file exports HTTP handlers.
// src/routes/api/posts/+server.js
import { json } from "@sveltejs/kit";
export async function GET() {
const posts = await db.post.findMany();
return json(posts);
}
export async function POST({ request }) {
const body = await request.json();
const post = await db.post.create({ data: body });
return json(post, { status: 201 });
}
These are ordinary web Request and Response objects, so the same knowledge transfers to other runtimes and frameworks.
Hooks
A hooks.server.js file runs on every request. It is the place for authentication, logging and populating event.locals.
// src/hooks.server.js
export async function handle({ event, resolve }) {
const session = await getSession(event.cookies);
event.locals.user = session?.user ?? null;
return resolve(event);
}
Anything you set on locals is available to load functions and actions, which keeps cross-cutting concerns in one place instead of scattered across routes.
Rendering and adapters
SvelteKit supports several rendering strategies and lets you choose per route:
- SSR renders on the server and hydrates in the browser.
- Prerendering generates static HTML at build time.
- CSR renders only in the browser for routes that opt out.
- Hybrid mixes all three, so a marketing page can be static while an app route is server-rendered.
Deployment is handled by adapters. Install the adapter for your target — Node, static, Vercel, Netlify, Cloudflare and more — configure it, and the same source builds for that platform. Switching hosts is usually a one-line change.
Best practices
- Use
+page.server.jsload functions for data that must stay on the server. - Prefer form actions over client-side POST requests for mutations.
- Keep authentication and logging in
hooks.server.js. - Choose the narrowest rendering mode that fits each route.
- Use
+layout.sveltefor shared UI so state persists across navigation. - Type your load function return values when using TypeScript.
- Install an adapter that matches your deployment target from the start.
Common mistakes
- Fetching in
onMountand losing server rendering. - Putting secrets in a universal
+page.jsload instead of+page.server.js. - Rebuilding forms with client-side fetch when actions already work.
- Forgetting to key
{#each}blocks and breaking list updates. - Prerendering a route that depends on per-user data.
- Ignoring hooks and duplicating auth checks in every load function.
Where to go next
SvelteKit is the complete way to build with Svelte. Deepen your Svelte knowledge, add TypeScript, and compare the architecture with Next.js, Nuxt and Astro. Then build a small app with a load function, a form action and an API endpoint to see the whole model in action.