Skip to main content
At the heart of SvelteKit is a filesystem-based router. The routes of your app — the URL paths that users can access — are defined by the directories in your codebase.

Basic routing

Routes are created by adding files to the src/routes directory:
src/routes/+page.svelte
You can change src/routes to a different directory by editing the project configuration.

Route files

Each route directory contains one or more route files, which can be identified by their + prefix.

Key routing rules

Server execution

All files can run on the server

Client execution

All files run on the client except +server files

Inheritance

+layout and +error files apply to subdirectories

Page components

+page.svelte

A +page.svelte component defines a page of your app. By default, pages are rendered both on the server (SSR) for the initial request and in the browser (CSR) for subsequent navigation.
<!--- file: src/routes/+page.svelte --->
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
SvelteKit uses <a> elements to navigate between routes, rather than a framework-specific <Link> component.

Receiving data

Pages can receive data from load functions via the data prop:
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>

Receiving params

As of SvelteKit 2.24, pages also receive a params prop typed based on the route parameters:
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
	import { getPost } from '../blog.remote';

	/** @type {import('./$types').PageProps} */
	let { params } = $props();

	const post = $derived(await getPost(params.slug));
</script>

<h1>{post.title}</h1>
<div>{@html post.content}</div>

Load functions

+page.js

Often, a page will need to load some data before it can be rendered. Add a +page.js module that exports a load function:
/// file: src/routes/blog/[slug]/+page.js
import { error } from '@sveltejs/kit';

/** @type {import('./$types').PageLoad} */
export function load({ params }) {
	if (params.slug === 'hello-world') {
		return {
			title: 'Hello world!',
			content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
		};
	}

	error(404, 'Not found');
}
This function runs alongside +page.svelte, which means it runs on the server during server-side rendering and in the browser during client-side navigation.

+page.server.js

If your load function can only run on the server — for example, if it needs to fetch data from a database or access private environment variables — rename +page.js to +page.server.js:
/// file: src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
import * as db from '$lib/server/database';

/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
	const post = await db.getPost(params.slug);

	if (post) {
		return post;
	}

	error(404, 'Not found');
}
During client-side navigation, SvelteKit will load this data from the server, which means that the returned value must be serializable using devalue.

Layouts

+layout.svelte

To create a layout that applies to every page, make a file called src/routes/+layout.svelte:
<!--- file: src/routes/+layout.svelte --->
<script>
	let { children } = $props();
</script>

<nav>
	<a href="/">Home</a>
	<a href="/about">About</a>
	<a href="/settings">Settings</a>
</nav>

{@render children()}

Nested layouts

Layouts can be nested. For example, create a layout that only applies to pages below /settings:
<!--- file: src/routes/settings/+layout.svelte --->
<script>
	/** @type {import('./$types').LayoutProps} */
	let { data, children } = $props();
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

{@render children()}

+layout.js

Your +layout.svelte component can get data from a load function in +layout.js:
/// file: src/routes/settings/+layout.js
/** @type {import('./$types').LayoutLoad} */
export function load() {
	return {
		sections: [
			{ slug: 'profile', title: 'Profile' },
			{ slug: 'notifications', title: 'Notifications' }
		]
	};
}
Data returned from a layout’s load function is available to all its child pages.

Server routes

You can define routes with a +server.js file (API routes), which gives you full control over the response:
/// file: src/routes/api/random-number/+server.js
import { error } from '@sveltejs/kit';

/** @type {import('./$types').RequestHandler} */
export function GET({ url }) {
	const min = Number(url.searchParams.get('min') ?? '0');
	const max = Number(url.searchParams.get('max') ?? '1');

	const d = max - min;

	if (isNaN(d) || d < 0) {
		error(400, 'min and max must be numbers, and min must be less than max');
	}

	const random = min + Math.random() * d;

	return new Response(String(random));
}

HTTP methods

Your +server.js file can export functions corresponding to HTTP verbs:
export function GET({ url }) {
	// Handle GET request
}

Error handling

+error.svelte

If an error occurs during load, SvelteKit will render a default error page. Customize this error page on a per-route basis by adding an +error.svelte file:
<!--- file: src/routes/blog/[slug]/+error.svelte --->
<script>
	import { page } from '$app/state';
</script>

<h1>{page.status}: {page.error.message}</h1>
SvelteKit will ‘walk up the tree’ looking for the closest error boundary. If no +error.svelte file exists, it will try parent directories before rendering the default error page.

Type safety

SvelteKit creates a $types.d.ts file for type safety when working with route files:
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();
</script>
If you’re using VS Code or any IDE that supports the language server protocol and TypeScript plugins, you can omit these types entirely — Svelte’s IDE tooling will insert the correct types for you.

Next steps