Skip to main content

Concepts principaux

Routing

Éditer cette page sur Github

Le cœur de SvelteKit est un routeur basé sur l'arborescence de fichiers. Les routes de votre application — c'est-à-dire les URLs auxquelles les utilisateurs et utilisatrices ont accès — sont définies 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 dynamiquement lorsque quelqu'un demande une page comme /blog/hello-world

Vous pouvez utiliser un autre dossier que src/routes en modifiant la configuration du projet.

Chaque dossier de route continent un ou plusieurs fichiers de route, que vous pouvez reconnaître à leur préfixe +.

+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) lors la requête initiale et dans le navigateur (CSR) pour les navigations suivantes.

src/routes/+page.svelte
<h1>Bonjour et bienvenue sur mon site !</h1>
<a href="/about">À propos de mon site</a>
src/routes/about/+page.svelte
<h1>À propos de ce site</h1>
<p>À FAIRE...</p>
<a href="/">Accueil</a>
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

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

Notez que SvelteKit utilise des éléments <a> pour naviguer entre les routes, plutôt qu'un composant <Link> qui serait spécifique au framework.

+page.js

Souvent, une page a besoin de charger des données avant qu'elle ne puisse être rendue. Pour cela, il suffit d'ajouter un module +page.js qui exporte une fonction load :

src/routes/blog/[slug]/+page.js
ts
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, 'Introuvable');
}
src/routes/blog/[slug]/+page.ts
ts
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, 'Introuvable');
};

Cette fonction s'exécute avec +page.svelte, ce qui signifie qu'elle est exécutée sur le serveur 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 trouverez plus d'informations sur ces valeurs dans la section options de page.

+page.server.js

Si votre fonction load doit impérativement être exécutée sur le serveur — par exemple, si elle a besoin de récupérer des données dans une base de données, ou si elle a besoin d'accéder à 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 PageLoad en PageServerLoad.

src/routes/blog/[slug]/+page.server.js
ts
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, 'Introuvable');
}
src/routes/blog/[slug]/+page.server.ts
ts
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, 'Introuvable');
};

Lors de la navigation côté client, SvelteKit va charger cette donnée sur le serveur, ce qui implique que la valeur renvoyée doit être sérialisable avec devalue. Voir load pour plus de détails sur l'API.

Comme +page.js, +page.server.js peut exporter des options de pageprerender, 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, les actions vous permettent d'écrire des données sur le serveur en utilisant l'élément <form>. Pour savoir comment vous en servir, reportez-vous à la section sur les actions de formulaire.

+error

Si une erreur survient durant load, SvelteKit affiche une page d'erreur par défaut. Vous pouvez personnaliser cette page d'erreur en fonction de la route en ajoutant un fichier +error.svelte :

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

<h1>{$page.status}: {$page.error.message}</h1>
src/routes/blog/[slug]/+error.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

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

SvekteKit "remonte l'arborescence de fichiers" à la recherche de la page d'erreur la plus proche — si le fichier de l'exemple ci-dessus n'existe pas, il essaie de trouver src/routes/blog/+error.svelte puis src/routes/+error.svelte avant d'afficher la page d'erreur par défaut. Si cette page échoue également (ou si l'erreur survient au sein de la fonction load du +layout racine, qui est "au-dessus" du fichier +error racine), SvelteKit sauve les meubles et rend une page d'erreur statique, que vous pouvez personnaliser en créant un fichier src/error.html.

Si l'erreur survient dans la fonction load d'un +layout(.server).js, le fichier d'erreur le plus proche dans l'arbre est un fichier +error.svelte au-dessus de ce layout (et non au même niveau).

Si aucune route n'est trouvée (404), src/routes/+error.svelte est affichée (ou la page d'erreur par défaut, si ce fichier n'existe pas).

+error.svelte n'est pas utilisée lorsqu'une erreur survient dans une fonction handle ou un gestionnaire de requête +server.js.

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

+layout

Jusqu'ici, nous avons traité les pages comme des composants complètement autonomes — lors de la navigation, le composant +page.svelte actuel est détruit, et un autre prend sa place.

Mais dans beaucoup d'applications, certains éléments doivent être visibles sur chaque page, comme la navigation principale ou le footer. Plutôt que de tous les répéter dans chaque +page.svelte, nous pouvons les définir dans des layouts.

+layout.svelte

Pour créer un layout qui s'applique à chaque page, créez un fichier avec le nom src/routes/+layout.svelte. Le layout par défaut (celui que SvelteKit utilise si vous ne créez pas le votre) ressemble à ça...

<slot></slot>

... mais nous pouvons y ajouter n'importe quel markup, style ou comportement. La seule contrainte est la présence d'un <slot> représentant le contenu de la page. Par exemple, ajoutons un barre de navigation :

src/routes/+layout.svelte
<nav>
	<a href="/">Accueil</a>
	<a href="/about">À propos</a>
	<a href="/settings">Paramètres</a>
</nav>

<slot></slot>

Si nous créeons des pages pour /, /about, et /settings...

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

... la barre de navigation sera alors toujours visible, et passer d'une page à l'autre ne remplacera que le <h1>.

Les layouts peuvent être imbriqués. Supposez que nous ayons plusieurs pages de paramètres – /settings, /settings/profile et /settings/notifications – avec un sous-menu partagé (un exemple est disponible sur github.com/settings).

Nous pouvons créer un layout qui s'applique aux pages en-dessous de /settings (tout en héritant de la barre de navigation du layout racine) :

src/routes/settings/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<h1>Paramètres</h1>

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

<slot></slot>
src/routes/settings/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<h1>Paramètres</h1>

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

<slot></slot>

Par défaut, chaque layout hérite du layout au-dessus de lui. Mais parfois ce n'est pas ce que l'on souhaite — dans ce cas, la section layouts avancés peut vous aider.

+layout.js

De la même manière que +page.svelte charge sa donnée de +page.js, votre composant +layout.svelte peut charger sa donnée dans la fonction load du fichier +layout.js.

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

Si un +layout.js exporte des options de pageprerender, ssr et csr — elles seront appliquées par défaut aux pages enfantes.

La donnée renvoyée d'une fonction load de layout est aussi accessible dans toutes ses pages enfants :

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

	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
src/routes/settings/profile/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>

Souvent, la donnée de layout reste inchangée lors de la navigation entre pages. SvelteKit ré-exécute intelligemment les fonctions load uniquement lorsque nécessaire.

+layout.server.js

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

Comme +layout.js, +layout.server.js peut exporter des options de pageprerender, ssr et csr.

+server

En plus des pages, vous pouvez définir des routes à l'aide de fichiers +server.js (parfois appelées "routes d'API" ou "endpoint"), qui vous donnent un contrôle complet sur votre réponse. Un fichier +server.js exporte des fonctions correspondant aux verbes HTTP comme GET, POST, PATCH, PUT, DELETE, OPTIONS, et HEAD qui acceptent un argument RequestEvent et renvoie un objet Response.

Par exemple, nous pouvons créer une route /api/random-number avec une fonction GET :

src/routes/api/random-number/+server.js
ts
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 inférieur à max');
}
const random = min + Math.random() * d;
return new Response(String(random));
}
src/routes/api/random-number/+server.ts
ts
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 inférieur à max');
}
const random = min + Math.random() * d;
return new Response(String(random));
};

Le premier argument de la Response peut être un ReadableStream, rendant possible l'envoi d'une grande quantité de données sous forme de flux, ou la création d'évènements-serveur (sauf si vous déployez sur des plateformes qui envoient les réponses sous forme de Buffer, comme AWS Lambda).

Vous pouvez utiliser les méthodes error, redirect et json de @sveltejs/kit pour vous simplifier la vie (mais vous n'en avez pas l'obligation).

Si une erreur survient (soit via error(...), soit une erreur inattendue), la réponse sera la représentation JSON de l'erreur ou une page d'erreur de secours — qui peut être personnalisée via src/error.html — en fonction du header Accept. Le composant +error.svelte ne sera pas rendu dans ce cas. Vous pouvez en apprendre plus sur la gestion d'erreurs ici.

Lors que vous créez une fonction OPTIONS, notez que Vite injectera les headers Access-Control-Allow-Origin et Access-Control-Allow-Methods — ceux-ci ne sont pas présents en production à moins que vous ne les ajoutiez.

Recevoir des données

En exportant des fonctions 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.svelte
<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}>Calculer</button>
src/routes/add/+page.svelte
<script lang="ts">
	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}>Calculer</button>
src/routes/api/add/+server.js
ts
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
src/routes/api/add/+server.ts
ts
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 façon d'envoyer des données du navigateur vers le serveur.

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

Fonction par défaut

Exporter la fonction fallback permet de gérer toutes les méthodes HTTP non spécifiées, incluant des méthodes comme MOVE qui n'ont pas d'export dédié dans +server.js.

src/routes/api/add/+server.js
ts
import { json, text } from '@sveltejs/kit';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// Cette fonction gèrera PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
return text(`J'ai géré votre requête ${request.method} !`);
}
src/routes/api/add/+server.ts
ts
import { json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// Cette fonction gèrera PUT, PATCH, DELETE, etc.
export const fallback: RequestHandler = async ({ request }) => {
return text(`J'ai géré votre requête ${request.method} !`);
};

Pour les requêtes HEAD, la fonction GET est prioritaire sur la fonction fallback.

Négociation de contenu

Les fichiers +server.js peuvent être placés dans le même dossier que les fichiers +page, permettant à une route d'être à la fois une page ou un endpoint d'API. Pour les différencier, SvelteKit utilise les règles suivantes :

  • les requêtes PUT/PATCH/DELETE/OPTIONS sont toujours gérées par +server.js puisqu'elles ne concernent pas les pages
  • les requêtes GET/POST/HEAD sont traitées comme des requêtes de page si le header accept priorise text/html (en d'autres mots, c'est un navigateur qui demande une page), et sinon gérées par +server.js.
  • les réponses aux requêtes GET incluent un header Vary: Accept, de sorte que les proxy et les navigateurs mettent en cache séparement les réponses HTML et JSON.

$types

Dans les exemples précédents, nous avons importé les types d'un fichier $types.d.ts. C'est un fichier que SvelteKit crée pour vous dans un dossier caché si vous utilisez TypeScript (ou JavaScript avec les annotations typées JSDoc) pour vous garantir du typage lorsque vous travaillez avec vos fichiers principaux.

Par exemple, annoter export let data avec PageData (ou LayoutData, pour un fichier +layout.svelte) dit à TypeScript que le type de data est du type de ce que renvoie la fonction load :

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

En retour, annoter la fonction load avec PageLoad, PageServerLoad, LayoutLoad ou LayoutServerLoad (pour +page.js, +page.server.js, +layout.js et +layout.server.js respectivement) garantit que les params et la valeur de retour sont correctement typées.

Si vous utilisez VS Code ou tout IDE qui supporte le protocole language server ainsi que des plugins TypeScript, vous pouvez alors omettre complètement ces types ! L'outillage d'IDE de Svelte ajoutera les bons types à votre place, pour que vous ayez la vérification de types sans avoir à l'écrire vous-même. Cela fonctionne également avec notre outil de ligne de commande svelte-check.

Vous pouvez en apprendre plus sur l'omission des $types dans cet article de blog (en anglais).

Autres fichiers

Tout autre fichier présent dans un dossier de route est ignoré par SvelteKit. Cela implique que vous pouvez placer vos composants et utilitaires à côté des routes qui en ont besoin.

Si des composants et utilitaires sont utilisés par plusieurs routes, c'est une bonne idée de les placer dans le dossier $lib.

Sur le même sujet