Routing
At the heart of SvelteKit is a filesystem-based router. The routes of your app — i.e. the URL paths that users can access — are defined by the directories in your codebase:
src/routes
is the root routesrc/routes/about
creates an/about
routesrc/routes/blog/[slug]
creates a route with a parameter,slug
, that can be used to load data dynamically when a user requests a page like/blog/hello-world
You can change
src/routes
to a different directory by editing the project config.
Each route directory contains one or more route files, which can be identified by their +
prefix.
We’ll introduce these files in a moment in more detail, but here are a few simple rules to help you remember how SvelteKit’s routing works:
- All files can run on the server
- All files run on the client except
+server
files +layout
and+error
files apply to subdirectories as well as the directory they live in
+page
+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.
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
<h1>About this site</h1>
<p>TODO...</p>
<a href="/">Home</a>
Pages can receive data from load
functions via the data
prop.
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>
Legacy mode
In Svelte 4 In Svelte 4, you’d use
export let data
instead
Note that SvelteKit uses
<a>
elements to navigate between routes, rather than a framework-specific<Link>
component.
+page.js
Often, a page will need to load some data before it can be rendered. For this, we add a +page.js
module that exports a load
function:
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function function load({ params }: {
params: any;
}): {
title: string;
content: string;
}
load({ params: any
params }) {
if (params: any
params.slug === 'hello-world') {
return {
title: string
title: 'Hello world!',
content: string
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = ({ params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
if (params: Record<string, any>
The parameters of the current page - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug === 'hello-world') {
return {
title: string
title: 'Hello world!',
content: string
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
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. See load
for full details of the API.
As well as load
, +page.js
can export values that configure the page’s behaviour:
export const prerender = true
orfalse
or'auto'
export const ssr = true
orfalse
export const csr = true
orfalse
You can find more information about these in page options.
+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 you need to access private environment variables like API keys — then you can rename +page.js
to +page.server.js
and change the PageLoad
type to PageServerLoad
.
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) {
const const post: {
title: string;
content: string;
}
post = await const getPostFromDatabase: (slug: string) => {
title: string;
content: string;
}
getPostFromDatabase(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug);
if (const post: {
title: string;
content: string;
}
post) {
return const post: {
title: string;
content: string;
}
post;
}
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not found');
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async ({ params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params }) => {
const const post: {
title: string;
content: string;
}
post = await const getPostFromDatabase: (slug: string) => {
title: string;
content: string;
}
getPostFromDatabase(params: Record<string, any>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.slug);
if (const post: {
title: string;
content: string;
}
post) {
return const post: {
title: string;
content: string;
}
post;
}
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
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. See load
for full details of the API.
Like +page.js
, +page.server.js
can export page options — prerender
, ssr
and csr
.
A +page.server.js
file can also export actions. If load
lets you read data from the server, actions
let you write data to the server using the <form>
element. To learn how to use them, see the form actions section.
+error
If an error occurs during load
, SvelteKit will render a default error page. You can customise this error page on a per-route basis by adding an +error.svelte
file:
<script>
import { page } from '$app/stores';
</script>
<h1>{$page.status}: {$page.error.message}</h1>
SvelteKit will ‘walk up the tree’ looking for the closest error boundary — if the file above didn’t exist it would try src/routes/blog/+error.svelte
and then src/routes/+error.svelte
before rendering the default error page. If that fails (or if the error was thrown from the load
function of the root +layout
, which sits ‘above’ the root +error
), SvelteKit will bail out and render a static fallback error page, which you can customise by creating a src/error.html
file.
If the error occurs inside a load
function in +layout(.server).js
, the closest error boundary in the tree is an +error.svelte
file above that layout (not next to it).
If no route can be found (404), src/routes/+error.svelte
(or the default error page, if that file does not exist) will be used.
+error.svelte
is not used when an error occurs insidehandle
or a +server.js request handler.
You can read more about error handling here.
+layout
So far, we’ve treated pages as entirely standalone components — upon navigation, the existing +page.svelte
component will be destroyed, and a new one will take its place.
But in many apps, there are elements that should be visible on every page, such as top-level navigation or a footer. Instead of repeating them in every +page.svelte
, we can put them in layouts.
+layout.svelte
To create a layout that applies to every page, make a file called src/routes/+layout.svelte
. The default layout (the one that SvelteKit uses if you don’t bring your own) looks like this...
<script>
let { children } = $props();
</script>
{@render children()}
...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a @render
tag for the page content. For example, let’s add a nav bar:
<!- 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()}
If we create pages for /
, /about
and /settings
...
<h1>Home</h1>
<h1>About</h1>
<h1>Settings</h1>
...the nav will always be visible, and clicking between the three pages will only result in the <h1>
being replaced.
Layouts can be nested. Suppose we don’t just have a single /settings
page, but instead have nested pages like /settings/profile
and /settings/notifications
with a shared submenu (for a real-life example, see github.com/settings).
We can create a layout that only applies to pages below /settings
(while inheriting the root layout with the top-level nav):
<script>
/** @type {{ data: import('./$types').LayoutData, children: import('svelte').Snippet }} */
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()}
<script lang="ts">
import type { LayoutData } from './$types';
import type { Snippet } from 'svelte';
let { data, children }: { data: LayoutData, children: Snippet } = $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()}
You can see how data
is populated by looking at the +layout.js
example in the next section just below.
By default, each layout inherits the layout above it. Sometimes that isn’t what you want - in this case, advanced layouts can help you.
+layout.js
Just like +page.svelte
loading data from +page.js
, your +layout.svelte
component can get data from a load
function in +layout.js
.
/** @type {import('./$types').LayoutLoad} */
export function function load(): {
sections: {
slug: string;
title: string;
}[];
}
load() {
return {
sections: {
slug: string;
title: string;
}[]
sections: [
{ slug: string
slug: 'profile', title: string
title: 'Profile' },
{ slug: string
slug: 'notifications', title: string
title: 'Notifications' }
]
};
}
import type { type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad } from './$types';
export const const load: LayoutLoad
load: type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type LayoutLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
LayoutLoad = () => {
return {
sections: {
slug: string;
title: string;
}[]
sections: [
{ slug: string
slug: 'profile', title: string
title: 'Profile' },
{ slug: string
slug: 'notifications', title: string
title: 'Notifications' }
]
};
};
If a +layout.js
exports page options — prerender
, ssr
and csr
— they will be used as defaults for child pages.
Data returned from a layout’s load
function is also available to all its child pages:
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently rerun
load
functions when necessary.
+layout.server.js
To run your layout’s load
function on the server, move it to +layout.server.js
, and change the LayoutLoad
type to LayoutServerLoad
.
Like +layout.js
, +layout.server.js
can export page options — prerender
, ssr
and csr
.
+server
As well as pages, you can define routes with a +server.js
file (sometimes referred to as an ‘API route’ or an ‘endpoint’), which gives you full control over the response. Your +server.js
file exports functions corresponding to HTTP verbs like GET
, POST
, PATCH
, PUT
, DELETE
, OPTIONS
, and HEAD
that take a RequestEvent
argument and return a Response
object.
For example we could create an /api/random-number
route with a GET
handler:
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export function function GET({ url }: {
url: any;
}): Response
GET({ url: any
url }) {
const const min: number
min = var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(url: any
url.searchParams.get('min') ?? '0');
const const max: number
max = var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(url: any
url.searchParams.get('max') ?? '1');
const const d: number
d = const max: number
max - const min: number
min;
if (function isNaN(number: number): boolean
Returns a Boolean value that indicates whether a value is the reserved value NaN (not a number).
isNaN(const d: number
d) || const d: number
d < 0) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(400, 'min and max must be numbers, and min must be less than max');
}
const const random: number
random = const min: number
min + var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.Math.random(): number
Returns a pseudorandom number between 0 and 1.
random() * const d: number
d;
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request.
Response(var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(const random: number
random));
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export const const GET: RequestHandler
GET: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = ({ url: URL
The requested URL.
url }) => {
const const min: number
min = var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.get(name: string): string | null
Returns the first value associated to the given search parameter.
get('min') ?? '0');
const const max: number
max = var Number: NumberConstructor
(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.get(name: string): string | null
Returns the first value associated to the given search parameter.
get('max') ?? '1');
const const d: number
d = const max: number
max - const min: number
min;
if (function isNaN(number: number): boolean
Returns a Boolean value that indicates whether a value is the reserved value NaN (not a number).
isNaN(const d: number
d) || const d: number
d < 0) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(400, 'min and max must be numbers, and min must be less than max');
}
const const random: number
random = const min: number
min + var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.Math.random(): number
Returns a pseudorandom number between 0 and 1.
random() * const d: number
d;
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request.
Response(var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(const random: number
random));
};
The first argument to Response
can be a ReadableStream
, making it possible to stream large amounts of data or create server-sent events (unless deploying to platforms that buffer responses, like AWS Lambda).
You can use the error
, redirect
and json
methods from @sveltejs/kit
for convenience (but you don’t have to).
If an error is thrown (either error(...)
or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via src/error.html
— depending on the Accept
header. The +error.svelte
component will not be rendered in this case. You can read more about error handling here.
When creating an
OPTIONS
handler, note that Vite will injectAccess-Control-Allow-Origin
andAccess-Control-Allow-Methods
headers — these will not be present in production unless you add them.
Receiving data
By exporting POST
/ PUT
/PATCH
/DELETE
/OPTIONS
/HEAD
handlers, +server.js
files can be used to create a complete API:
<script>
let a = 0;
let b = 0;
let total = 0;
async function add() {
const response = await fetch('/api/add', {
method: 'POST',
body: JSON.stringify({ a, b }),
headers: {
'content-type': 'application/json'
}
});
total = await response.json();
}
</script>
<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}
<button on:click={add}>Calculate</button>
import { function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function function POST({ request }: {
request: any;
}): Promise<Response>
POST({ request: any
request }) {
const { const a: any
a, const b: any
b } = await request: any
request.json();
return function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json(const a: any
a + const b: any
b);
}
import { function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json } from '@sveltejs/kit';
import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export const const POST: RequestHandler
POST: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = async ({ request: Request
The original request object
request }) => {
const { const a: any
a, const b: any
b } = await request: Request
The original request object
request.Body.json(): Promise<any>
json();
return function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json(const a: any
a + const b: any
b);
};
In general, form actions are a better way to submit data from the browser to the server.
If a
GET
handler is exported, aHEAD
request will return thecontent-length
of theGET
handler’s response body.
Fallback method handler
Exporting the fallback
handler will match any unhandled request methods, including methods like MOVE
which have no dedicated export from +server.js
.
import { function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json, function text(body: string, init?: ResponseInit | undefined): Response
Create a Response
object from the supplied body.
text } from '@sveltejs/kit';
export async function function POST({ request }: {
request: any;
}): Promise<Response>
POST({ request: any
request }) {
const { const a: any
a, const b: any
b } = await request: any
request.json();
return function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json(const a: any
a + const b: any
b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function function fallback({ request }: {
request: any;
}): Promise<Response>
fallback({ request: any
request }) {
return function text(body: string, init?: ResponseInit | undefined): Response
Create a Response
object from the supplied body.
text(`I caught your ${request: any
request.method} request!`);
}
import { function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json, function text(body: string, init?: ResponseInit | undefined): Response
Create a Response
object from the supplied body.
text } from '@sveltejs/kit';
import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export async function function POST({ request }: {
request: any;
}): Promise<Response>
POST({ request: any
request }) {
const { const a: any
a, const b: any
b } = await request: any
request.json();
return function json(data: any, init?: ResponseInit | undefined): Response
Create a JSON Response
object from the supplied data.
json(const a: any
a + const b: any
b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
export const const fallback: RequestHandler
fallback: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = async ({ request: Request
The original request object
request }) => {
return function text(body: string, init?: ResponseInit | undefined): Response
Create a Response
object from the supplied body.
text(`I caught your ${request: Request
The original request object
request.Request.method: string
Returns request’s HTTP method, which is “GET” by default.
method} request!`);
};
For
HEAD
requests, theGET
handler takes precedence over thefallback
handler.
Content negotiation
+server.js
files can be placed in the same directory as +page
files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules:
PUT
/PATCH
/DELETE
/OPTIONS
requests are always handled by+server.js
since they do not apply to pagesGET
/POST
/HEAD
requests are treated as page requests if theaccept
header prioritisestext/html
(in other words, it’s a browser page request), else they are handled by+server.js
.- Responses to
GET
requests will include aVary: Accept
header, so that proxies and browsers cache HTML and JSON responses separately.
$types
Throughout the examples above, we’ve been importing types from a $types.d.ts
file. This is a file SvelteKit creates for you in a hidden directory if you’re using TypeScript (or JavaScript with JSDoc type annotations) to give you type safety when working with your root files.
For example, annotating let { data } = $props()
with PageData
(or LayoutData
, for a +layout.svelte
file) tells TypeScript that the type of data
is whatever was returned from load
:
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
</script>
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
In turn, annotating the load
function with PageLoad
, PageServerLoad
, LayoutLoad
or LayoutServerLoad
(for +page.js
, +page.server.js
, +layout.js
and +layout.server.js
respectively) ensures that params
and the return value are correctly typed.
If you’re using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types entirely! Svelte’s IDE tooling will insert the correct types for you, so you’ll get type checking without writing them yourself. It also works with our command line tool svelte-check
.
You can read more about omitting $types
in our blog post about it.
Other files
Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them.
If components and modules are needed by multiple routes, it’s a good idea to put them in $lib
.