Routage et Chargement de Données dans SvelteKit
Bienvenue dans ce module de "Maîtriser SvelteKit : Développement Web Réactif et Ultra-Performant" ! Aujourd'hui, nous allons plonger au cœur de l'architecture de SvelteKit : son système de routage basé sur les fichiers et ses mécanismes avancés de chargement de données. Comprendre ces concepts est fondamental pour bâtir des applications SvelteKit performantes, réactives et faciles à maintenir.
SvelteKit, un framework "meta" construit au-dessus de Svelte, excelle par sa capacité à offrir une expérience de développement ergonomique tout en produisant des applications web extrêmement optimisées. L'une de ses forces majeures réside dans la manière dont il gère la navigation entre les pages (le routage) et l'alimentation de ces pages en informations (le chargement de données), le tout de manière intégrée et intuitive.
I. Le Routage Basé sur les Fichiers : La Simplicité Redéfinie
SvelteKit adopte une approche du routage qui ravira ceux qui préfèrent la clarté : le routage basé sur le système de fichiers. Cela signifie que la structure de vos fichiers et dossiers dans le répertoire src/routes dicte directement l'architecture de votre application web.
1. Les Bases du Routage
Chaque dossier dans src/routes représente un segment d'URL, et les fichiers +page.svelte à l'intérieur de ces dossiers définissent les pages qui seront rendues.
- Page d'accueil :
src/routes/+page.sveltesera accessible via/. - Page "À propos" :
src/routes/about/+page.sveltesera accessible via/about. - Page "Contact" :
src/routes/contact/+page.sveltesera accessible via/contact.
C'est simple, intuitif et permet une excellente organisation de votre code.
2. Routes Dynamiques
SvelteKit gère très bien les routes dynamiques, c'est-à-dire les routes dont une partie de l'URL est variable (par exemple, l'ID d'un utilisateur ou le slug d'un article de blog). Pour ce faire, vous utilisez des crochets [] autour du nom du segment de l'URL.
Exemple : Pour une page affichant les détails d'un article de blog :
src/routes/blog/[slug]/+page.svelte
Ici, [slug] est un paramètre dynamique. Si l'utilisateur navigue vers /blog/mon-premier-article, la valeur de slug sera mon-premier-article. Ce paramètre sera accessible dans vos fonctions de chargement de données, comme nous le verrons plus tard.
3. Layouts (Mises en page)
Les layouts permettent de partager des composants d'interface utilisateur (comme un en-tête, un pied de page ou une barre de navigation latérale) entre plusieurs pages.
- Un fichier
+layout.sveltedans un dossier s'applique à toutes les pages et sous-routes de ce dossier. - Le layout racine se trouve dans
src/routes/+layout.svelteet s'applique à toute l'application.
Dans un fichier +layout.svelte, vous devez utiliser le composant <slot /> pour indiquer où le contenu de la page enfant (ou du layout enfant) doit être inséré.
<!-- src/routes/+layout.svelte -->
<script>
// Logique du layout, si nécessaire
</script>
<header>
<h1>Mon Blog SvelteKit</h1>
<nav>
<a href="/">Accueil</a>
<a href="/blog">Articles</a>
<a href="/about">À Propos</a>
</nav>
</header>
<main>
<!-- Le contenu de la page enfant (ou du layout enfant) sera inséré ici -->
<slot />
</main>
<footer>
<p>© 2023 Mon Super Blog</p>
</footer>
<style>
/* Styles pour le layout */
header { background-color: #f0f0f0; padding: 1rem; }
main { padding: 1rem; }
footer { background-color: #e0e0e0; padding: 1rem; text-align: center; }
</style>
4. Pages d'Erreur
Vous pouvez définir des pages d'erreur personnalisées pour gérer les erreurs non capturées (par exemple, une erreur 404 "Non trouvé" ou une erreur 500 "Serveur interne").
src/routes/+error.svelte: capture toutes les erreurs non gérées dans l'application.src/routes/blog/+error.svelte: capture les erreurs spécifiques aux routes sous/blog.
Le fichier +error.svelte reçoit un prop data avec les détails de l'erreur (status, message).
II. Chargement de Données : L'Intelligence Derrière SvelteKit
SvelteKit offre des mécanismes puissants et flexibles pour charger les données nécessaires à vos pages, qu'elles soient chargées côté serveur (pour le rendu initial, le SEO) ou côté client (pour les mises à jour dynamiques). Cette flexibilité est l'une des raisons principales de la performance de SvelteKit.
1. Les Fonctions load : Le Cœur du Chargement de Données
Le chargement de données dans SvelteKit se fait via des fonctions load exportées depuis des fichiers spécifiques :
+page.js: Fonctionloads'exécutant à la fois côté serveur et côté client.+page.server.js: Fonctionloads'exécutant uniquement côté serveur.+layout.js: Fonctionloads'exécutant à la fois côté serveur et côté client pour un layout.+layout.server.js: Fonctionloads'exécutant uniquement côté serveur pour un layout.
Distinction Cruciale : +page.js vs +page.server.js
-
+page.server.js: À privilégier pour la plupart de vos besoins en chargement de données.- Exécution : Exclusivité serveur. Idéal pour les requêtes à des bases de données, des API internes, ou des opérations nécessitant des clés API sensibles.
- Avantages : Améliore le temps de chargement initial (SSR), l'expérience utilisateur (pas de "flash" de contenu vide), et l'optimisation pour les moteurs de recherche (SEO). Les données sont directement disponibles avant l'envoi de la page au navigateur.
fetch: Utilise l'APIfetchstandard de Node.js, qui peut directement interroger des API internes ou des bases de données sans proxy.- Pas d'accès au DOM ou à l'objet
window.
-
+page.js: À utiliser avec parcimonie pour des cas spécifiques.- Exécution : Peut s'exécuter à la fois sur le serveur (lors du rendu initial) et sur le client (lors des navigations côté client, ou quand des données de layout changent).
- Avantages : Utile pour les données qui dépendent de l'interaction client (par exemple, la géolocalisation), ou si vous avez besoin de manipuler des données héritées d'un
+layout.jsparent qui ne sont pas disponibles sur le serveur. fetch: Utilise l'APIfetchdu navigateur lors de l'exécution côté client, et une version defetchdu serveur lors de l'exécution côté serveur. Pour interroger vos propres API SvelteKit (+server.js), il est important d'utiliser lefetchpassé à la fonctionloadpour bénéficier de l'optimisation des requêtes internes (pas de requêtes HTTP réelles).- Accès au DOM et à l'objet
window(uniquement côté client).
2. Le Paramètre params et l'Objet url
La fonction load reçoit un objet en paramètre avec des propriétés clés pour récupérer des informations contextuelles :
params: Contient les paramètres dynamiques de la route. Par exemple, poursrc/routes/blog/[slug]/+page.server.js,params.slugcontiendra la valeur du slug.url: Un objet URL représentant l'URL actuelle de la requête, y compris les paramètres de requête (url.searchParams).fetch: La fonctionfetchoptimisée de SvelteKit.parent: Une fonction qui permet de récupérer les données chargées par les layouts parents.data: (seulement dans+page.jsou+layout.js) contient les données retournées par la fonctionloadde son équivalent.server.jss'il existe.
// Exemple: src/routes/blog/[slug]/+page.server.js
export async function load({ params, url, fetch }) {
const { slug } = params; // Récupère le slug de l'URL
const searchTerm = url.searchParams.get('q'); // Récupère un paramètre de requête si présent
// Exemple de récupération de données depuis une API externe ou un service interne
const res = await fetch(`https://api.example.com/posts/${slug}`);
if (res.ok) {
const post = await res.json();
return {
post // Les données retournées seront disponibles dans la page Svelte sous `data.post`
};
}
// Gérer les erreurs (par exemple, article non trouvé)
throw new Error(`Article '${slug}' non trouvé.`); // SvelteKit redirigera vers +error.svelte
}
3. Utilisation des Données dans les Composants Svelte
Les données retournées par une fonction load sont passées au composant +page.svelte (ou +layout.svelte) via un prop data.
<!-- Exemple: src/routes/blog/[slug]/+page.svelte -->
<script>
export let data; // Déclare le prop 'data'
const { post } = data; // Destructure pour accéder aux données de l'article
</script>
<h1>{post.title}</h1>
<p>Auteur : {post.author}</p>
<article>
{@html post.content} <!-- Afficher le contenu HTML -->
</article>
<style>
/* Styles spécifiques à l'article */
</style>
4. Fonctions load dans les Layouts et parent()
Les fonctions load dans les layouts sont très utiles pour charger des données qui doivent être disponibles sur plusieurs pages (par exemple, les informations de l'utilisateur connecté, la navigation principale, etc.).
// Exemple: src/routes/+layout.server.js
// Charge les informations de l'utilisateur connecté pour toute l'application
export async function load({ cookies }) {
const token = cookies.get('session_token');
let user = null;
if (token) {
// Simuler une requête API pour obtenir les infos utilisateur
// const res = await fetch('/api/user', { headers: { Authorization: `Bearer ${token}` } });
// if (res.ok) {
// user = await res.json();
// }
user = { name: "Alice", email: "alice@example.com" }; // Données factices pour l'exemple
}
return { user };
}
Les pages enfants (ou layouts enfants) peuvent accéder aux données des layouts parents grâce à la fonction parent().
// Exemple: src/routes/dashboard/+page.js
// Accède aux données de l'utilisateur chargé par le layout racine
export async function load({ parent }) {
const { user } = await parent(); // Récupère les données du parent (+layout.server.js)
if (!user) {
// Redirige si l'utilisateur n'est pas connecté
throw redirect(302, '/login');
}
return {
dashboardData: "Bienvenue sur votre tableau de bord, " + user.name + "!"
};
}
Important : La fonction parent() attend d'être await-ée car elle retourne une promesse qui résout les données combinées de tous les parents.
5. Redirections et Erreurs dans load
- Redirection : Utilisez la fonction
redirectde@sveltejs/kit/navigation.import { redirect } from '@sveltejs/kit'; export async function load({ url }) { if (!loggedIn) { throw redirect(302, `/login?redirectTo=${url.pathname}`); } // ... } - Erreurs : Pour signaler une erreur (par exemple, 404 Not Found, 500 Internal Server Error), utilisez la fonction
errorde@sveltejs/kit/server. SvelteKit affichera alors votre page+error.svelte.import { error } from '@sveltejs/kit'; export async function load({ params }) { const post = await getPostFromDB(params.slug); if (!post) { throw error(404, 'Article non trouvé'); // Redirige vers +error.svelte avec le statut 404 } return { post }; }
6. Invalidation des Données
Lorsqu'une navigation SvelteKit se produit (navigation côté client), les fonctions load sont ré-exécutées pour la nouvelle page. Cependant, si des données sous-jacentes changent sans navigation (par exemple, après une soumission de formulaire qui met à jour une ressource), vous pouvez dire à SvelteKit de recharger les données en invalidant l'URL correspondante.
// Dans un composant Svelte ou une action de formulaire
import { invalidate } from '$app/navigation';
async function updateItem() {
await fetch('/api/item/123', { method: 'PUT', body: JSON.stringify({ /* ... */ }) });
invalidate('/api/item/123'); // Invalide les données pour cette URL, déclenchant le rechargement des `load` fonctions qui en dépendent.
// Vous pouvez aussi invalider des chemins plus génériques :
// invalidate((url) => url.pathname.startsWith('/admin/products'));
}
invalidate est particulièrement utile pour les mises à jour réactives après des opérations CUD (Create, Update, Delete) sans recharger toute la page.
III. Cas Pratique : Un Blog Simple
Pour illustrer le routage et le chargement de données, créons un blog très simple.
- Layout principal :
src/routes/+layout.svelteetsrc/routes/+layout.server.jspour charger l'utilisateur. - Page d'accueil :
src/routes/+page.sveltepour lister les articles. - Page d'article unique :
src/routes/blog/[slug]/+page.svelteetsrc/routes/blog/[slug]/+page.server.jspour charger un article spécifique.
Étape 1 : Le Layout Principal
src/routes/+layout.server.js
(Simule la récupération d'un utilisateur connecté)
// src/routes/+layout.server.js
/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
// En production, vous feriez une requête à votre backend pour valider le token
const sessionToken = cookies.get('session_token');
let user = null;
if (sessionToken === 'valid_token_123') { // Exemple simple
user = {
id: 'usr_123',
name: 'John Doe',
email: 'john.doe@example.com'
};
}
return { user }; // Rend l'objet 'user' disponible à tous les layouts et pages enfants
}
src/routes/+layout.svelte
(Affiche l'utilisateur si connecté, et contient la navigation)
<!-- src/routes/+layout.svelte -->
<script>
/** @type {import('./$types').LayoutData} */
export let data; // Reçoit les données de +layout.server.js
$: user = data.user; // Rend user réactif si les données changent (ex: après login/logout)
</script>
<header>
<nav>
<a href="/">Accueil</a>
<a href="/blog">Articles</a>
<a href="/about">À Propos</a>
</nav>
{#if user}
<p>Bonjour, {user.name} !</p>
{:else}
<p><a href="/login">Se connecter</a></p>
{/if}
</header>
<main>
<slot /> <!-- Ici s'affiche le contenu de la page actuelle -->
</main>
<footer>
<p>© 2023 Mon Blog SvelteKit</p>
</footer>
<style>
header { display: flex; justify-content: space-between; align-items: center; background: #333; color: white; padding: 1rem; }
nav a { color: white; margin-left: 1rem; text-decoration: none; }
main { max-width: 960px; margin: 2rem auto; padding: 0 1rem; }
footer { text-align: center; padding: 1rem; background: #eee; }
</style>
Étape 2 : Page d'Accueil des Articles
src/routes/blog/+page.server.js
(Charge la liste des articles depuis le serveur)
// src/routes/blog/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load({ fetch }) {
// Simule une API interne ou externe
const response = await fetch('https://api.example.com/posts'); // Remplacez par votre API réelle
const posts = await response.json();
if (response.ok) {
return { posts };
} else {
// Gérer l'erreur si la récupération des articles échoue
throw new Error('Impossible de charger les articles.');
}
}
src/routes/blog/+page.svelte
(Affiche la liste des articles)
<!-- src/routes/blog/+page.svelte -->
<script>
/** @type {import('./$types').PageData} */
export let data; // Reçoit 'posts' de +page.server.js
const { posts } = data;
</script>
<h1>Tous les Articles</h1>
<ul>
{#each posts as post}
<li>
<a href={`/blog/${post.slug}`}>
<h2>{post.title}</h2>
</a>
<p>{post.description}</p>
</li>
{/each}
</ul>
<style>
ul { list-style: none; padding: 0; }
li { margin-bottom: 1.5rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
h2 { margin: 0; }
a { text-decoration: none; color: inherit; }
a:hover h2 { text-decoration: underline; }
</style>
Étape 3 : Page d'Article Unique
src/routes/blog/[slug]/+page.server.js
(Charge un article spécifique basé sur le slug)
// src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params, fetch }) {
const { slug } = params; // Récupère le slug de l'URL dynamique
// Requête vers votre API pour récupérer l'article par son slug
const response = await fetch(`https://api.example.com/posts/${slug}`);
const post = await response.json();
if (response.ok) {
return { post };
} else {
// Si l'article n'est pas trouvé (par exemple, 404), lance une erreur
throw error(response.status, `Article '${slug}' non trouvé.`);
}
}
src/routes/blog/[slug]/+page.svelte
(Affiche les détails de l'article)
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
/** @type {import('./$types').PageData} */
export let data; // Reçoit 'post' de +page.server.js
const { post } = data;
</script>
<article>
<h1>{post.title}</h1>
<p class="author">Par {post.author} le {new Date(post.date).toLocaleDateString()}</p>
<div class="content">
{@html post.content} <!-- Utilisation de {@html} pour injecter du HTML brut -->
</div>
</article>
<style>
article { max-width: 800px; margin: 0 auto; padding: 2rem 0; }
h1 { font-size: 2.5rem; margin-bottom: 0.5rem; }
.author { color: #666; font-style: italic; margin-bottom: 1.5rem; }
.content { line-height: 1.6; }
</style>
Conclusion
Le système de routage basé sur les fichiers et les mécanismes de chargement de données de SvelteKit sont des piliers fondamentaux pour la construction d'applications web modernes et performantes. En combinant la simplicité d'une structure de fichiers intuitive avec la puissance des fonctions load exécutées côté serveur ou client, SvelteKit vous permet de :
- Organiser votre application de manière logique et facile à comprendre.
- Optimiser les performances grâce au rendu côté serveur (SSR) et au pré-rendu (SSG).
- Améliorer l'expérience utilisateur avec des chargements de pages rapides et réactives.
- Faciliter le développement en intégrant le chargement de données directement dans la structure de la route.
En maîtrisant ces concepts, vous êtes désormais armé pour créer des applications SvelteKit robustes, rapides et maintenables, exploitant pleinement la puissance de ce framework de nouvelle génération. Pensez toujours à la source de vos données et à la nature de votre page pour choisir judicieusement entre +page.js et +page.server.js, en privilégiant +page.server.js pour la majorité de vos besoins afin de maximiser les avantages du SSR.