Skip to main content

Hooks

Les “hooks” sont des fonctions que vous pouvez déclarer et que SvelteKit va appeler en réponse à des évènements spécifiques, vous permettant de contrôler finement le comportement de l’application.

Il y a trois fichiers de hooks, tous optionnels :

  • src/hooks.server.js — les hooks serveur de votre application
  • src/hooks.client.js — les hooks client de votre application
  • src/hooks.js — les hooks de votre application qui peuvent être exécutés à la fois sur le client et sur le serveur

Le code dans ces modules s’exécutera lors du démarrage de l’application, les rendant utile pour l’initialisation de clients de bases de données, parmi d’autres choses.

handle

Peut être ajoutée dans le fichier src/hooks.server.js

Cette fonction est exécutée à chaque fois que le serveur SvelteKit reçoit une requête — que cela se produise lors de l’exécution de l’application ou lors du pré-rendu — et en détermine la réponse. Elle reçoit un objet event représentant la requête et une fonction appelée resolve, qui fait le rendu de la route et génère une Response. Ceci vous permet de modifier les en-têtes ou body de réponse, ou de contourner complètement SvelteKit (pour par exemple implémenter des routes programmatiquement).

src/hooks.server
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
	if (event.url.pathname.startsWith('/custom')) {
		return new Response('réponse personnalisée');
	}

	const response = await resolve(event);
	return response;
}
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	if (event.url.pathname.startsWith('/custom')) {
		return new Response('réponse personnalisée');
	}

	const response = await resolve(event);
	return response;
};

Les requêtes demandant des assets statiques — incluant les pages déjà prérendues — ne sont pas gérées par SvelteKit.

Si le hook handle est exécutée en tant que partie d’une requête de fonction distante initiée par le client, route, params et url sont relatifs à la page depuis laquelle la fonction distante a été appelée, et non l’URL du endpoint que SvelteKit crée pour la fonction distante. Ne les utilisez jamais pour déterminer si oui ou non un utilisateur ou utilisatrice est autorisée à accéder à certaines données, puisque ces valeurs font partie de la requête et peuvent donc être manipulées. les queries ne sont de plus pas ré-exécutées lorsque l’utilisateur ou l’utilisatrice navigue (à moins que l’argument de la query ait changé suite à une navigation), vous devriez donc être conscient•e de la façon dont vous utilisez ces valeurs.

Si non implémentée, cette fonction vaut par défaut ({ event, resolve }) => resolve(event).

Durant le prérendu, Svelte parcourt vos pages à la recherche de liens et rend chaque route qu’il trouve. Le fait de rendre la route provoque l’exécution de la fonction handle (et de toute autre dépendance de route, comme load). Si vous avez besoin d’empêcher du code d’être exécuté lors de cette phase, vérifier en amont que l’application n’est pas en train d’être compilée (via building.

Vous pouvez définir plusieurs fonctions handle et les exécuter avec la fonction utilitaire sequence.

resolve supporte également un deuxième paramètre optionnel qui vous donne plus de contrôle sur la manière dont la réponse va être rendue. Ce paramètre est un objet qui peut avoir les champs suivants :

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — applique des transformations personnalisées au HTML. Si done vaut true, il s’agit du dernier fragment. Il n’y a pas de garantie que chaque fragment individuel soit du HTML bien formé (il se peut qu’un fragment inclut la balise ouvrante d’un élément mais pas sa balise fermante, par exemple) mais ils seront toujours découpés à des endroits clés tels que %sveltekit.head ou les composants de layout ou de page.
  • filterSerializedResponseHeaders(name: string, value: string): boolean — détermine quels en-têtes devraient être inclus dans les réponses sérialisées lorsqu’une fonction load charge une ressource avec fetch. Par défaut, aucun ne sera inclus.
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean — détermine quels fichiers devraient être ajoutés à la balise <head> pour les précharger. La méthode est appelée avec chaque fichier trouvé pendant la construction des fragments de code au moment de la compilation — donc si vous avez par exemple import './styles.css' dans votre fichier +page.svelte, preload sera appelé avec le chemin résolu menant à ce fichier CSS lorsque la page sera visitée. Notez qu’en mode développement preload n’est pas appelé, puisqu’il dépend de l’analyse se produisant lors de la compilation. Le préchargement peut améliorer la performance en téléchargeant les assets plus tôt, mais cela peut aussi la pénaliser si trop de choses sont téléchargées sans raison. Par défaut, les fichiers js et css seront préchargés. Les fichiers asset ne sont actuellement pas préchargés du tout, mais il est possible que cela soit ajouté à SvelteKit plus tard, en fonction des retours sur le sujet.
src/hooks.server
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
	const response = await resolve(event, {
		transformPageChunk: ({ html }) => html.replace('old', 'new'),
		filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
		preload: ({ type, path }) => type === 'js' || path.includes('/important/')
	});

	return response;
}
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	const response = await resolve(event, {
		transformPageChunk: ({ html }) => html.replace('old', 'new'),
		filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
		preload: ({ type, path }) => type === 'js' || path.includes('/important/')
	});

	return response;
};

Notez que resolve(...) ne lèvera jamais d’erreur, mais renverra toujours une Promise<Response> avec le statut approprié. Si une erreur est levée ailleurs lors de l’exécution de handle, elle est traitée comme étant fatale, et SvelteKit répondra avec une représentation JSON de l’erreur ou avec la page d’erreur de secours — qui peut être personnalisée via src/error.html — selon la valeur de l’en-tête Accept. Vous pouvez en apprendre plus à propos de la gestion des erreurs here.

locals

Pour ajouter des données personnalisées à la requête, qui seront ensuite passées aux gestionnaires des fichiers +server.js et aux fonctions load, remplissez l’objet event.locals, comme montré ci-dessous.

src/hooks.server
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
	event.locals.user = await getUserInformation(event.cookies.get('sessionid'));

	const response = await resolve(event);

	// Notez que modifier les en-têtes de réponse n'est pas toujours sécurisé.
	// Les objets de réponse peuvent avoir des en-têtes immutables (par ex.
	// Response.redirect() renvoyé par un endpoint).
	// Modifier les en-têtes immutables jette une erreur TypeError.
	// Dans ce cas, clonez la réponse ou évitez de créer un objet de réponse
	// ayant des en-têtes immutables.
	response.headers.set('x-custom-header', 'potato');

	return response;
}
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	event.locals.user = await getUserInformation(event.cookies.get('sessionid'));

	const response = await resolve(event);

	// Notez que modifier les en-têtes de réponse n'est pas toujours sécurisé.
	// Les objets de réponse peuvent avoir des en-têtes immutables (par ex.
	// Response.redirect() renvoyé par un endpoint).
	// Modifier les en-têtes immutables jette une erreur TypeError.
	// Dans ce cas, clonez la réponse ou évitez de créer un objet de réponse
	// ayant des en-têtes immutables.
	response.headers.set('x-custom-header', 'potato');

	return response;
};

handleFetch

Peut être ajoutée dans le fichier src/hooks.server.js

Cette fonction vous permet de modifier (ou remplacer) le résultat d’un appel event.fetch se produisant sur le serveur (ou pendant le pré-rendu) au sein d’un endpoint, ou d’une fonction load, action, handle, handleError or reroute.

Par exemple, imaginons que votre fonction load fasse une requête vers une URL publique comme https://api.yourapp.com lorsqu’un utilisateur ou utilisatrice effectue une navigation côté client vers la page concernée. Lors du rendu serveur de cette page, cela aurait du sens d’appeler directement l’API (en contournant tout éventuel proxy ou gestionnaire de charge qui pourrait se trouver entre l’API et internet).

src/hooks.server
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
	if (request.url.startsWith('https://api.yourapp.com/')) {
		// clone la requête originale, mais change l'URL
		request = new Request(
			request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
			request
		);
	}

	return fetch(request);
}
import type { HandleFetch } from '@sveltejs/kit';

export const handleFetch: HandleFetch = async ({ request, fetch }) => {
	if (request.url.startsWith('https://api.yourapp.com/')) {
		// clone la requête originale, mais change l'URL
		request = new Request(
			request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
			request
		);
	}

	return fetch(request);
};

Les requêtes faites avec event.fetch suivent le modèle d’authentification du navigateur — pour les requêtes de même origine, les en-têtes cookie et authorization sont relayés à moins que l’option credentials soit définie comme "omit". Pour les requêtes d’origine différente (cross-origin), cookie sera inclus si l’URL de la requête appartient à un sous-domaine de l’application — par exemple si votre appliation est sur my-domain.com et que votre API est sur api.my-domain.com, les cookies seront inclus dans la requête.

Il y a tout de même une limitation : si votre application et votre API sont sur des sous-domaines frères — www.my-domain.com et api.my-domain.com par exemple — alors un cookie appartenant à un domaine parent commun comme my-domain.com ne sera pas inclus, car SvelteKit n’a aucun moyen de savoir à quel domaine appartient le cookie. Dans ces situations vous devrez manuellement inclure le cookie en utilisant handleFetch :

src/hooks.server
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
	if (request.url.startsWith('https://api.my-domain.com/')) {
		request.headers.set('cookie', event.request.headers.get('cookie'));
	}

	return fetch(request);
}
import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
	if (request.url.startsWith('https://api.my-domain.com/')) {
		request.headers.set('cookie', event.request.headers.get('cookie'));
	}

	return fetch(request);
};

handleValidationError

Peut être ajoutée dans le fichier src/hooks.server.js

Ce hook est appelé lorsqu’une fonction distante est appelée avec un argument qui ne correspond pas au Standard Schema fourni. Il doit renvoyer un objet correspondant à la forme de App.Error.

Si par exemple vous avez une fonction distante qui attend une chaîne de caractères comme argument...

todos.remote
import * as v from 'valibot';
import { query } from '$app/server';

export const getTodo = query(v.string(), (id) => {
	// implémentation...
});

... mais est exécutée avec quelque chose qui ne correspond pas au schéma — comme un nombre (par ex. await getTodos(1)) — la validation va alors échouer, le serveur va répondre avec un code 400, et la fonction va jeter une erreur avec le message ‘Bad Request’.

Pour personnaliser ce message et ajouter des propriétés additionnelles à l’objet d’erreur, implémentez handleValidationError :

src/hooks.server
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function handleValidationError({ issues }) {
	return {
		message: 'Non merci'
	};
}
import type { HandleValidationError } from '@sveltejs/kit';

export const handleValidationError: HandleValidationError = ({ issues }) => {
	return {
		message: 'Non merci'
	};
};

Soyez conscient•e des informations que vous exposez ici, car la raison d’échec de validation la plus probable est que quelqu’un de mal intentionné envoie des requêtes mal formées à votre serveur.

handleError

Peut être ajoutée aux fichiers src/hooks.server.js et src/hooks.client.js

Si une erreur imprévue est levée lors du chargement, du rendu, ou de l’exécution d’un endpoint, cette fonction sera exécutée avec les arguments error, event, status et message. Cela permet deux choses :

  • vous pouvez afficher l’erreur
  • vous pouvez générer une représentation personnalisée de l’erreur que vous pouvez afficher aux utilisateurs et utilisatrices, nettoyée des informations sensibles comme les messages ou les stack traces. La valeur renvoyée, qui vaut par défaut { message }, devient la valeur de page.error.

Pour les erreurs jetées depuis votre code (ou depuis du code d’une librairie appelée depuis votre code), le statut sera 500 et le message sera “Internal Error”. Bien que error.message peut contenir des informations sensibles qui ne devraient pas être exposées aux utilisateurs et utilisatrices, message ne pose pas de problèmes (bien que sans grand intérêt pour une personne lambda).

Pour ajouter plus d’informations à l’objet page.error de manière typée, vous pouvez personnalisée la forme attendue en déclarant une interface App.Error (qui doit inclure message: string, pour garantir un comportement par défaut correct). Cela vous permet de — par exemple — ajouter un ID de suivi que les utilisateurs et utilisatrices pourront utiliser comme référence lors de leurs échanges potentiels avec vos équipes de support :

src/app.d
declare global {
	namespace App {
		interface Error {
			message: string;
			errorId: string;
		}
	}
}

export {};
src/hooks.server
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
	const errorId = crypto.randomUUID();

	// exemple d'intégration avec https://sentry.io/
	Sentry.captureException(error, {
		extra: { event, errorId, status }
	});

	return {
		message: 'Oups !',
		errorId
	};
}
import * as Sentry from '@sentry/sveltekit';
import type { HandleServerError } from '@sveltejs/kit';

Sentry.init({/*...*/})

export const handleError: HandleServerError = async ({ error, event, status, message }) => {
	const errorId = crypto.randomUUID();

	// exemple d'intégration avec https://sentry.io/
	Sentry.captureException(error, {
		extra: { event, errorId, status }
	});

	return {
		message: 'Oups !',
		errorId
	};
};
src/hooks.client
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
	const errorId = crypto.randomUUID();

	// exemple d'intégration avec https://sentry.io/
	Sentry.captureException(error, {
		extra: { event, errorId, status }
	});

	return {
		message: 'Oups !',
		errorId
	};
}
import * as Sentry from '@sentry/sveltekit';
import type { HandleClientError } from '@sveltejs/kit';

Sentry.init({/*...*/})

export const handleError: HandleClientError = async ({ error, event, status, message }) => {
	const errorId = crypto.randomUUID();

	// exemple d'intégration avec https://sentry.io/
	Sentry.captureException(error, {
		extra: { event, errorId, status }
	});

	return {
		message: 'Oups !',
		errorId
	};
};

Dans le fichier src/hooks.client.js, le type de handleError est HandleClientError au lieu de HandleServerError, et event est un NavigationEvent plutôt qu’un RequestEvent.

Cette fonction n’est pas appelée pour les erreurs prévues (celles jetées depuis la fonction error importée depuis @sveltejs/kit).

Lors du développement, si une erreur se produit à cause d’une erreur de syntaxe dans votre code Svelte, l’erreur fournie aura une propriété frame mettant en valeur la position de l’erreur.

Assurez-vous que handleError ne jette jamais d’erreur.

init

Peut être ajoutée aux fichiers src/hooks.server.js et src/hooks.client.js

Cette fonction est exécutée une seule fois, lorsque le serveur est créé ou lorsque l’application démarre dans le navigateur. C’est une fonction pratique pour effectuer des opérations asynchrones comme l’initialisation d’une connexion à une base de données.

Si votre environnement support le fait d’écrire await à la racine de votre fichier, utiliser la fonction init n’est pas vraiment différente d’initialiser votre logique d’initialisation à la racine du module, mais certains environnements — le plus connu étant Safari — ne le supportent pas.

src/hooks.server
import * as db from '$lib/server/database';

/** @type {import('@sveltejs/kit').ServerInit} */
export async function init() {
	await db.connect();
}
import * as db from '$lib/server/database';
import type { ServerInit } from '@sveltejs/kit';

export const init: ServerInit = async () => {
	await db.connect();
};

Dans le navigateur, les tâches asynchrones au sein de init vont retarder l’hydratation, soyez donc conscient•e de ce que vous y faites.

reroute

Peut être ajoutée au fichier src/hooks.js ; elle est exécutée à la fois sur le serveur et sur le client.

Cette fonction est exécutée avant handle et vous permet de changer la manière dont les URLs sont traduites en routes. Le chemin renvoyé (qui vaut par défaut url.pathname) est utilisé pour sélectionner la route et ses paramètres.

Par exemple, vous pourriez avoir une page src/routes/[[lang]]/about/+page.svelte, qui devrait être accessible en tant que /en/about, /de/ueber-uns ou /fr/a-propos. Vous pourriez implémenter ceci avec reroute :

src/hooks

/** @type {Record<string, string>} */
const translated = {
	'/en/about': '/en/about',
	'/de/ueber-uns': '/de/about',
	'/fr/a-propos': '/fr/about',
};

/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
	if (url.pathname in translated) {
		return translated[url.pathname];
	}
}
import type { Reroute } from '@sveltejs/kit';
const translated: Record<string, string> = {
	'/en/about': '/en/about',
	'/de/ueber-uns': '/de/about',
	'/fr/a-propos': '/fr/about',
};

export const reroute: Reroute = ({ url }) => {
	if (url.pathname in translated) {
		return translated[url.pathname];
	}
};

Le paramètre lang sera correctement déduit du chemin renvoyé.

Le fait d’utiliser reroute ne changera pas le contenu de la barre d’adresse, ni la valeur de event.url.

Depuis la version 2.18, le hook reroute peut être asynchrone, vous permettant de (par exemple) récupérer des données depuis votre backend pour décider vers quelle route re-router. Assurez-vous que la récupération de données soit rapide, car cela retardera la navigation sinon. Si vous avez besoin de récupérer des données, utilisez la fonction fetch fourni en argument. Celle-ci hérite des mêmes bénéfices que la fonction fetch fournie aux fonctions load, avec l’inconvénient que params et id ne seront pas rendus disponibles pour handleFetch car la route n’est à ce moment-là pas encore déterminée.

src/hooks

/** @type {import('@sveltejs/kit').Reroute} */
export async function reroute({ url, fetch }) {
	// Demandez à un endpoint spécial de votre application quelle est la destination
	if (url.pathname === '/api/reroute') return;

	const api = new URL('/api/reroute', url);
	api.searchParams.set('pathname', url.pathname);

	const result = await fetch(api).then(r => r.json());
	return result.pathname;
}
import type { Reroute } from '@sveltejs/kit';
export const reroute: Reroute = async ({ url, fetch }) => {
	// Demandez à un endpoint spécial de votre application quelle est la destination
	if (url.pathname === '/api/reroute') return;

	const api = new URL('/api/reroute', url);
	api.searchParams.set('pathname', url.pathname);

	const result = await fetch(api).then(r => r.json());
	return result.pathname;
};

La fonction reroute est considérée comme une fonction pure et idempotent. Par conséquence, elle doit toujours renvoyer le même résultat pour une entrée donnée, et ne doit pas avoir d’effets de bord. Avec ces considérations, SvelteKit va cacher le résultat de reroute sur le client afin que reroute ne soit exécutée qu’une seule fois par URL unique.

transport

Peut être ajoutée au fichier src/hooks.js ; elle est exécutée à la fois sur le serveur et sur le client.

Une collection de transporteurs, qui vous permettent de fournir des types personnalisés — renvoyés depuis une fonction load, au travers de la frontière serveur/client. Chaque transporteur contient une fonction encode, qui encode les valeurs sur le serveur (ou renvoie une valeur falsy pour ce qui n’est pas une instance du type) et une fonction decode correspondante :

src/hooks
import { Vector } from '$lib/math';

/** @type {import('@sveltejs/kit').Transport} */
export const transport = {
	Vector: {
		encode: (value) => value instanceof Vector && [value.x, value.y],
		decode: ([x, y]) => new Vector(x, y)
	}
};
import { Vector } from '$lib/math';
import type { Transport } from '@sveltejs/kit';

export const transport: Transport = {
	Vector: {
		encode: (value) => value instanceof Vector && [value.x, value.y],
		decode: ([x, y]) => new Vector(x, y)
	}
};

Plus de lecture

Modifier cette page sur Github llms.txt

précédent suivant