Skip to main content

Routing

Au coeur de SvelteKit se trouve un routeur basé sur le système de fichiers. Les routes de votre application — c-à-d les chemins d’URL auquels les utilisateurs et utilisatrices peuvent accéder — sont définis par les dossiers de votre projet :

  • src/routes est la route racine
  • src/routes/about crée une route /about
  • src/routes/blog/[slug] crée une route avec un paramètre, slug, qui peut être utilisé pour charger des données de manière dynamique lorsqu’un utilisateur ou utilisatrice requête une page comme /blog/hello-world

Vous pouvez changer src/routes pour utiliser un dossier différent en modifiant la configuration de votre projet.

Chaque dossier de route contient un ou plusieurs fichiers de route, qui sont identifiables à leur préfixe +.

Nous présenterons bientôt ces fichiers dans le détail, mais voici déjà quelques règles simples pour vous aider à vous souvenir comment le routing de SvelteKit fonctionne :

  • Tous les fichiers peuvent être exécutés sur le serveur
  • Tous les fichiers peuvent être exécutés côté client, sauf les fichiers +server
  • Les fichiers +layout et +error s’appliquent au dossier dans lequel ils sont définis, ainsi qu’à tous les sous-dossiers de ce dossier

Lorsque vous naviguez de la page A à la page B, SvelteKit conserve les composants communs à ces deux pages — voir ici pour plus d’informations.

+page

+page.svelte

Un composant +page.svelte définit une page de votre application. Par défaut, les pages sont rendues à la fois sur le serveur (SSR) pour la requête initiale, et dans le navigateur (CSR) pour les navigations suivantes.

src/routes/+page
<h1>Bonjour et bienvenue sur mon site !</h1>
<a href="/about">À propose de mon site</a>
src/routes/about/+page
<h1>À propos de ce site</h1>
<p>TODO...</p>
<a href="/">Accueil</a>

SvelteKit utilise les éléments <a> pour naviguer entre les routes, plutôt qu’un composant spécifique au framework, comme <Link> par exemple.

Les pages peuvent recevoir des données des fonctions load via la prop data.

src/routes/blog/[slug]/+page
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>
<script lang="ts">
	import type { PageProps } from './$types';

	let { data }: PageProps = $props();
</script>

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

Depuis la version 2.24, les pages recoivent également une prop params typée selon les paramètres de route. Ceci est particulièrement utile en combinaison avec les fonctions distantes :

src/routes/blog/[slug]/+page
<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>
<script lang="ts">
	import { getPost } from '../blog.remote';
	import type { PageProps } from './$types';
	let { params }: PageProps = $props();

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

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

PageProps a été ajouté dans la version 2.16.0 de SvelteKit. Dans les versions antérieures, vous deviez typer les propriétés data manuellement avec PageData, voir $types.

En Svelte 4, vous deviez utiliser export let data.

+page.js

Souvent, une page a besoin de charger des données avant de pouvoir être rendue. Pour cela, nous pouvons ajouter un module +page.js qui exporte une fonction load :

src/routes/blog/[slug]/+page
import { error } from '@sveltejs/kit';

/** @type {import('./$types').PageLoad} */
export function load({ params }) {
	if (params.slug === 'hello-world') {
		return {
			title: 'Bonjour tout le monde !',
			content: 'Bienvenue sur notre blog. Lorem ipsum dolor sit amet...'
		};
	}

	error(404, 'Not found');
}
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = ({ params }) => {
	if (params.slug === 'hello-world') {
		return {
			title: 'Bonjour tout le monde !',
			content: 'Bienvenue sur notre blog. Lorem ipsum dolor sit amet...'
		};
	}

	error(404, 'Not found');
};

Cette fonction est exécutée avec +page.svelte, ce qui signifie qu’elle est exécutée lors du rendu côté serveur, et dans le navigateur lors de la navigation côté client. Voir load pour plus de détails sur l’API.

En plus de load, +page.js peut exporter des valeurs qui configurent le comportement de la page :

  • export const prerender = true ou false ou 'auto'
  • export const ssr = true ou false
  • export const csr = true ou false

Vous pouvez trouver plus d’informations sur ces options dans le chapitre Options de page.

+page.server.js

Si votre fonction load ne peut pas être exécutée ailleurs que sur le serveur — par exemple, si elle a besoin de récupérer des données depuis une base de données ou si vous avez besoin de lire des variables d’environnement privées comme des clés d’API — vous pouvez alors renommer +page.js en +page.server.js et changer le type de PageLoad en PageServerLoad.

src/routes/blog/[slug]/+page.server
import { error } from '@sveltejs/kit';

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

	if (post) {
		return post;
	}

	error(404, 'Not found');
}
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
	const post = await getPostFromDatabase(params.slug);

	if (post) {
		return post;
	}

	error(404, 'Not found');
};

Lors de la navigation côté client, SvelteKit va charger ces données sur le serveur, ce qui signifie que la valeur renvoyée doit être sérialisable par devalue. Voir le chapitre load pour plus de détails sur l’API.

Comme +page.js, +page.server.js peut exporter des options de page — prerender, ssr et csr.

Un fichier +page.server.js peut également exporter des actions. Si load vous permet de lire des données sur le serveur, actions vous permet d’écrire des données sur le serveur en utilisant l’élément <form>. Pour apprendre comment vous en servir, voir la section sur les actions de formulaire.

+error

Si une erreur se produit pendant l’exécution de load, SvelteKit va rendre une page d’erreur par défaut. Vous pouvez personnaliser cette page d’erreur route par route en ajoutant un fichier +error.svelte :

src/routes/blog/[slug]/+error
<script>
	import { page } from '$app/state';
</script>

<h1>{page.status}: {page.error.message}</h1>
<script lang="ts">
	import { page } from '$app/state';
</script>

<h1>{page.status}: {page.error.message}</h1>
Legacy mode

$app/state a été ajouté dans la version 2.12 de SvelteKit. Si vous utilisez une version plus ancienne ou si vous utilisez Svelte 4, utilisez plutôt $app/stores.

SvelteKit va “remonter à la racine” à la recherche de la frontière d’erreur la plus proche — si le fichier dans l’exemple ci-dessus n’existait pas, il essaierait src/routes/blog/+error.svelte, puis src/routes/+error.svelte avant de rendre la page d’erreur par défaut. Si ceci échoue (ou si l’erreur a été levée depuis la fonction load du +layout racine, qui est conceptuellement “au-dessus” du fichier +error racine), SvelteKit va sauver les meubles et rendre une page d’erreur statique de secours, que vous pouvez personnaliser en créant un fichier src/error.html.

Si l’erreur se produit dans une fonction load d’un +layout(.server).js, la frontière d’erreur la plus proche dans l’arbre est un fichier +error.svelte “au-dessus” de ce layout (et non au même “endroit”).

Si aucune route ne peut être trouvée (404), le fichier src/routes/+error.svelte (ou la page d’erreur par défaut si ce fichier n’existe pas), sera utilisé.

+error.svelte n’est pas utilisé lorsqu’une erreur se produit au sein de ‘handle’ ou d’un gestionnaire de requête +server.js.

Vous pouvez en apprendre plus sur la gestion des erreurs ici.

+layout

Jusqu’à maintenant, nous avons traité les pages comme des composants autonomes — lors d’une navigation, le composant +page.svelte existant est détruit, et un nouveau vient le remplacer.

Mais dans beaucoup d’applications, certains éléments doivent être visibles sur chaque page, comme par exemple les éléments principaux de navigation ou un pied-de-page. Plutôt que les répéter dans chaque fichier +page.svelte, nous pouvons les mettre dans des layouts.

+layout.svelte

Pour créer un layout qui s’applique à toutes les pages, créez un fichier appelé src/routes/+layout.svelte. Le layout par défaut (celui que SvelteKit utilise si vous n’en créez pas un vous-même) ressemble à ça...

<script>
	let { children } = $props();
</script>

{@render children()}

... mais nous pouvons ajouter n’importe quel markup, style, ou comportement. Le chose requise est que le composant doit inclure une balise @render pour afficher le contenu de la page. Par exemple, ajoutons une barre de navigation :

src/routes/+layout
<script>
	let { children } = $props();
</script>

<nav>
	<a href="/">Accueil</a>
	<a href="/about">À propos</a>
	<a href="/settings">Paramètres</a>
</nav>

{@render children()}
<script lang="ts">
	let { children } = $props();
</script>

<nav>
	<a href="/">Accueil</a>
	<a href="/about">À propos</a>
	<a href="/settings">Paramètres</a>
</nav>

{@render children()}

Si nous créons les pages correspondant à /, /about et /settings...

src/routes/+page
<h1>Accueil</h1>
src/routes/about/+page
<h1>À propos</h1>
src/routes/settings/+page
<h1>Paramètres</h1>

... la barre de navigation sera toujours visible, et la navigation entre ces trois pages ne va se traduire uniquement par le remplacement du <h1>.

Les layouts peuvent être imbriqués. Supposez que n’ayons pas une seule page /settings, mais plutôt des pages imbriquées comme /settings/profiles et /settings/notifications avec un sous-menu commun (vous pouvez retrouver un exemple de la vraie vie sur la page github.com/settings).

Nous pouvons créer un layout qui s’applique uniquement aux pages sous la route /settings (tout en héritant du layout racine qui contient la barre de navigation principale) :

src/routes/settings/+layout
<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()}
<script lang="ts">
	import type { LayoutProps } from './$types';

	let { data, children }: LayoutProps = $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()}
Legacy mode

LayoutProps a été ajouté dans la version 2.16.0 de SvelteKit. Dans les versions antérieures, vous deviez typer les propriétés manuellement.

Vous pouvez maintenant constater la manière dont data est remplie en inspectant l’exemple de +layout.js de la prochaine section, juste en-dessous.

Par défaut, chaque layout hérite du layout au-dessus de lui. Parfois ce n’est pas ce que souhaitez — dans ce cas, les layouts avancés peuvent être utiles.

+layout.js

Tout comme +page.svelte vient charger ses données de +page.js, un composant +layout.svelte peut charger ses données depuis la fonction load d’un +layout.js.

src/routes/settings/+layout
/** @type {import('./$types').LayoutLoad} */
export function load() {
	return {
		sections: [
			{ slug: 'profile', title: 'Profil' },
			{ slug: 'notifications', title: 'Notifications' }
		]
	};
}
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = () => {
	return {
		sections: [
			{ slug: 'profile', title: 'Profil' },
			{ slug: 'notifications', title: 'Notifications' }
		]
	};
};

Si un fichier +layout.js exporte des options de pages — prerender, ssr et csr — ces options seront utilisées comme valeurs par défaut pour les pages enfant.

Les données renvoyées par la fonction load d’un layout sont également disponibles pour toutes ses pages enfant :

src/routes/settings/profile/+page
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();

	console.log(data.sections); // [{ slug: 'profile', title: 'Profil' }, ...]
</script>
<script lang="ts">
	import type { PageProps } from './$types';

	let { data }: PageProps = $props();

	console.log(data.sections); // [{ slug: 'profile', title: 'Profil' }, ...]
</script>

Souvent, les données de layout ne changent pas lors de la navigation entre pages. SvelteKit ré-exécute intelligemment les fonctions load uniquement lorsque c’est nécessaire.

+layout.server.js

Pour exécuter sur le serveur la fonction load de votre layout, déplacez-la dans un fichier +layout.server.js, et changer le type LayoutLoad en LayoutServerLoad.

Comme +layout.js, +layout.server.js peut exporter des options de page — prerender, ssr et csr.

+server

Comme pour les pages, vous pouvez définir des routes avec un fichier +server.js (parfois appelé “route d’API” ou “endpoint”), ce qui vous donne un contrôle total sur la réponse. Votre fichier +server.js exporte des fonctions correspondant aux verbes HTTP comme GET, POST, PATCH, PUT, DELETE, OPTIONS, et HEAD qui prennent en argument un RequestEvent et renvoient un objet Response.

Par exemple, nous pourrions créer une route /api/random-number avec un gestionnaire GET :

src/routes/api/random-number/+server
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 et max doivent être des nombres, et min doit être plus petit que max');
	}

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

	return new Response(String(random));
}
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = ({ 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 et max doivent être des nombres, et min doit être plus petit que max');
	}

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

	return new Response(String(random));
};

Le premier argument à Response peut être un ReadableStream, rendant possible le streaming de grandes quantité de données ou bien la création d’évènements envoyés par le serveur (à moins que vous déployiez sur des plateformes qui transforment les réponses en buffers, comme AWS Lambda).

Vous pouvez utiliser les méthodes error, redirect, et json de @sveltejs/kit pour des raisons pratiques (mais leur usage n’est pas nécessaire).

Si une erreur est jetée (soit avec error(...), soit par un évènement inattendu), la réponse sera une représentation JSON de l’erreur ou la page d’erreur de secours — qui peut être personnalisée via src/error.html — en fonction de l’en-tête Accept. Le composant +error.svelte ne sera pas rendu dans ce cas. Vous pouvez en savoir plus sur la gestion des erreurs ici.

Lorsque vous créez un gestionnaire OPTIONS, notez que Vite va injecter les en-têtes Acces-Control-Allow-Origin et Access-Control-Allow-Methods — ceux-ci ne seront présents en production à moins que vous les y ajoutiez.

Les fichiers +layout n’ont aucun effet sur les fichiers +server.js. Si vous souhaitez exécuter de la logique avant chaque requête, ajoutez-la dans le hook serveur handle.

Recevoir des données

En exportant des gestionnaires POST / PUT/PATCH/DELETE/OPTIONS/HEAD, les fichiers +server.js peuvent être utilisés pour créer une API complète :

src/routes/add/+page
<script>
	let a = $state(0);
	let b = $state(0);
	let total = $state(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 onclick={add}>Calculer</button>
<script lang="ts">
	let a = $state(0);
	let b = $state(0);
	let total = $state(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 onclick={add}>Calculer</button>
src/routes/api/add/+server
import { json } from '@sveltejs/kit';

/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
	const { a, b } = await request.json();
	return json(a + b);
}
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request }) => {
	const { a, b } = await request.json();
	return json(a + b);
};

En général, les actions de formulaire sont une meilleure manière de soumettre des données du navigateur vers le serveur.

Si un gestionnaire GET est exporté, une requête HEAD renverra la donnée content-length du body de réponse du gestionnaire GET.

Gestionnaire de méthode de secours

Exporter le gestionnaire fallback va matcher toute requête correspondant à une méthode non gérée, y compris les méthodes comme MOVE qui n’ont pas d’export dédié prévu par +server.js.

src/routes/api/add/+server
import { json, text } from '@sveltejs/kit';

/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
	const { a, b } = await request.json();
	return json(a + b);
}

// Ce gestionnaire va répondre à PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
	return text(`J'ai attrapé votre requête ${request.method} !`);
}
import { json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request }) => {
	const { a, b } = await request.json();
	return json(a + b);
};

// Ce gestionnaire va répondre à PUT, PATCH, DELETE, etc.

export const fallback: RequestHandler = async ({ request }) => {
	return text(`J'ai attrapé votre requête ${request.method} !`);
};

Pour les requêtes HEAD, le gestionnaire GET est prioritaire sur le gestionnaire fallback.

Négociation de contenu

Les fichiers +server.js peuvent être placés dans le même dossier que les fichiers +page, permettant à la même route d’être soit une page, soit un endpoint d’API. Pour les distinguer, SvelteKit applique les règles suivantes :

  • Les requêtes PUT / PATCH/DELETE/OPTIONS sont toujours gérées par +server.js puisque ces méthodes ne s’appliquent pas aux pages
  • Les requêtes GET / POST/HEAD sont traitées comme des requêtes de page si l’en-tête accept priorise text/html (en d’autres mots, si la requête vient d’une page ouverte par un navigateur), sinon elles seront traitées par +server.js.
  • Les réponses aux requêtes GET inclueront un en-tête Vary: Accept, de sorte que les proxies et les navigateurs puissent cacher les réponses HTML et JSON séparément.

$types

Dans les exemples ci-dessus, nous avons importé les types depuis un fichier $types.d.ts. Il s’agit d’un fichier que SvelteKit crée pour vous dans un dossier caché si vous utilisez TypeScript (ou JavaScript avec les annotations de type JSDoc) afin de vous fournir du typage lorsque vous travaillez sur vos fichiers de routes.

Par exemple, annoter let { data } = $props() avec PageProps (ou LayoutProps, pour un fichier +layout.svelte) indique à TypeScript que le type de data est défini par le type de retour de la fonction load correspondante :

src/routes/blog/[slug]/+page
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();
</script>
<script lang="ts">
	import type { PageProps } from './$types';

	let { data }: PageProps = $props();
</script>

Les types PageProps et LayoutProps, ajoutés dans la version 2.16.0, sont un raccourci de typer la prop data en tant que PageData ou LayoutData, ainsi que pour d’autres props, comme form pour les pages, ou children pour les layouts. Dans des versions antérieures, vous deviez typer ces propriétés manuellement. Par exemple, pour une page :

+page
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
let { data, form } = $props();
import type { PageData, ActionData } from './$types';

let { data, form }: { data: PageData, form: ActionData } = $props();

Ou, pour un layout :

+layout
/** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
let { data, children } = $props();
import type { LayoutData } from './$types';

let { data, children }: { data: LayoutData, children: Snippet } = $props();

De plus, annoter la fonction load avec PageLoad, PageServerLoad, LayoutLoad, ou LayoutServerLoad (pour respectivement +page.js, +page.server.js, +layout.js ou +layout.server.js) permet de s’assurer que params et la valeur de retour sont correctement typées.

Si vous utilisez VS Code ou tout IDE supportant le protocole LSP (Language Server Protocol) et les plugins TypeScript, vous pouvez omettre ces types complètement ! L’outillage d’IDE de Svelte va insérer les types adéquats à votre place, afin de profiter du typage des données de page et de layout sans que ayez à l’écrire vous-même. Cela fonctionne également au sein de notre outil en ligne de commande svelte-check.

Vous pouvez en apprendre plus sur le fait d’omettre $types dans notre article de blog dédié.

Autres fichiers

Tout autre fichier dans un dossier de route est ignoré par SvelteKit. Ceci signifie que vous pouvez ranger des composants et autres modules utilitaires dans les dossiers des routes qui les nécessitent.

Si des composants ou modules sont requis par plusieurs routes, c’est une bonne idée de les mettre dans le dossier $lib.

En savoir plus

Modifier cette page sur Github llms.txt