Skip to main content

Concepts avancés

Hooks

Éditer cette page sur Github

Les "hooks" sont des fonctions que vous déclarez dans votre application et que SvelteKit va exécuter en réponse à des évènements spécifiques, vous donnant un contrôle fin sur le comportement du framework.

Il existe 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 sont exécutés à la fois sur le client et sur le serveur

Le code de ces modules est exécuté lorsque l'application démarre, les rendant utiles pour initialiser par exemple des clients de bases de données.

Vous pouvez configurer l'emplacement de ces fichiers avec config.kit.files.hooks.

Hooks de serveur

Les hooks suivants peuvent être ajoutés au fichier src/hooks.server.js :

handle

Cette fonction est exécutée à chaque fois que le serveur SvelteKit reçoit une requête – que cette requête ait lieu pendant que l'application est en service ou pendant le processus de prérendu – et en détermine la réponse. La fonction reçoit un objet event représentant la requête et une fonction appelée resolve, qui rend la route et génère une Response. Cela vous permet de modifier les headers ou le body de réponse, ou de complètement contourner SvelteKit (pour implémenter des routes programmatiquement par exemple).

src/hooks.server.js
ts
/** @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;
}
src/hooks.server.ts
ts
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 vers des fichiers statiques – incluant les pages qui ont été prérendues – ne sont pas gérées par SvelteKit.

Si non implémentée, cette fonction sera considérée par défaut comme étant ({ event, resolve }) => resolve(event). Pour ajouter de la donnée personnalisée à la requête, qui est fournie aux fonctions de +server.js et aux fonctions load de serveur, utilisez l'objet event.locals, comme montré ci-dessous.

src/hooks.server.js
ts
/** @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);
response.headers.set('x-custom-header', 'patate');
return response;
}
src/hooks.server.ts
ts
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);
response.headers.set('x-custom-header', 'patate');
return response;
};

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

resolve supporte aussi un deuxième paramètre optionnel qui vous donne plus de contrôle sur la façon dont la réponse est générée. 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 morceau (chunk) de HTML. Les morceaux ne sont pas forcément du HTML bien formé (ils peuvent inclure la balise ouvrante d'un élément mais pas la balise fermante, par exemple), mais ils seront toujours découpés en fonctions de frontières sensibles, comme %sveltekit.head% ou les composants de page ou de layout.
  • filterSerializedResponseHeaders(name: string, value: string): boolean – détermine quels headers doivent ê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 doivent être ajoutés à la balise <head> pour les précharger. Cette méthode est appelée avec chacun des fichiers trouvés au moment de la compilation lorsque les morceaux de HTML sont construits – donc si vous avez par exemple import './styles.css' dans votre fichier +page.svelte, preload sera appelée avec le chemin résolu vers ce fichier CSS lorsque la page sera demandée. Notez qu'en mode développement, preload n'est pas exécutée, puisqu'elle dépend d'une analyse qui se produit à la compilation. Le préchargement peut améliorer vos performances en téléchargeant vos fichiers statiques plus tôt, mais peut aussi ralentir votre application si trop de fichiers sont téléchargés inutilement. Par défaut, les fichiers js et css seront préchargés. Les fichiers asset ne sont pas préchargés du tout actuellement, mais pourront l'être plus tard après analyse des retours d'expérience.
src/hooks.server.js
ts
/** @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;
}
src/hooks.server.ts
ts
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 déclenchera jamais d'erreur, elle renverra toujours une Promise<Response> avec le code de statut approprié. Si une erreur est déclenchée ailleurs pendant l'exécution de handle, elle sera traitée comme étant fatale, et SvelteKit y répondra avec une représentation JSON de l'erreur ou avec la page d'erreur par défaut – que vous pouvez personnaliser via src/error.html – en fonction du header Accept. Vous pouvez en savoir plus sur la gestion des erreurs dans ce chapitre.

handleFetch

Cette fonction vous permet de modifier (ou remplacer) une requête fetch qui se produit dans une fonction load ou action exécutée sur le serveur (ou pendant le prérendu).

Par exemple, votre fonction load pourrait faire une requête vers une URL publique comme https://api.yourapp.com lorsque votre utilisateur ou utilisatrice navigue côté client vers une page, mais lors du rendu côté serveur, cela peut être pertinent de requêter l'API directement (en évitant tout proxy ou load balancer qui se trouverait entre l'API et l'internet public).

src/hooks.server.js
ts
/** @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 en change l'URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
src/hooks.server.ts
ts
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 en change l'URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request,
);
}
return fetch(request);
};

Identifiants

Pour les requêtes de même origine, l'implémentation de fetch de SvelteKit va relayer les headers cookie et authorization à moins que l'option credentials soit définie à "omit".

Pour les requêtes d'origine différentes, le headercookie sera inclus si l'URL de requête appartient à un sous-domaine de l'application – par exemple si votre application est sur le domaine mon-domaine.com, et que votre API est sur le domaine api.mon-domaine.com, les cookies sont inclus dans la requête.

Si votre application et votre API sont sur des domaines frères – www.mon-domaine.com et api.mon-domaine.com par exemple – alors un cookie appartenant à un domain parent commun comme mon-domaine.com ne seront pas inclus, car SvelteKit n'a aucun moyen de savoir à quel domaine appartient le cookie. Dans ces cas-là, vous aurez besoin d'inclure le cookie manuellement en utilisant handleFetch :

src/hooks.server.js
ts
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.mon-domaine.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.2345Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.
}
return fetch(request);
}
src/hooks.server.ts
ts
import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
if (request.url.startsWith('https://api.mon-domaine.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.2345Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.
}
return fetch(request);
};

Hooks partagés

Les fonctions suivantes peuvent être ajoutées aux fichiers src/hooks.server.js et src/hooks.client.js :

handleError

Si une erreur inattendue se produit pendant le chargement ou le rendu, cette fonction sera exécutée avec error, event, status et message en argument. Cela permet deux choses :

  • vous pouvez afficher l'erreur
  • vous pouvez générer une représentation de l'erreur que vous affichez aux utilisateurs et utilisatrices, en y enlevant les données sensibles comme les messages ou les stack traces. La valeur renvoyée devient la valeur de $page.error.

Pour des erreurs venant de votre code (ou d'une librairie appelée par votre code), le statut sera 500 et le message "Internal Error". Alors que error.message peut contenir des données confidentielles, qui ne doivent pas être exposées aux utilisateurs et utilisatrices, message est sans risque (quoi qu'inutile pour un utilisateur lambda).

Pour ajouter des données supplémentaires à l'objet $page.error avec une validation des types, vous pouvez modifier l'interface App.Error (qui doit néanmoins inclure message: string pour garantir le comportement par défaut). Cela vous permet, par exemple, d'ajouter un identifiant de suivi que les utilisateurs et utilisatrices pourront citer dans leur correspondance avec votre personnel d'assistance technique :

src/app.d.ts
ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
}
export {};
src/hooks.server.js
ts
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 utilisant https://sentry.io/
Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2353Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Oups !',
errorId
};
}
src/hooks.server.ts
ts
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 utilisant https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId, status },
});
return {
message: 'Oups !',
errorId,
};
};
src/hooks.client.js
ts
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 utilisant https://sentry.io/
Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2353Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Oups !',
errorId
};
}
src/hooks.client.ts
ts
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 utilisant 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 plutôt que HandleServerError, et event est un NavigationEvent plutôt qu'un RequestEvent.

Cette fonction n'est pas appelée pour les erreurs attendues (celles déclenchées avec la fonction error importée depuis @sveltejs/kit).

Pendant le dévelopement, si une erreur se produit parce qu'une erreur de syntaxe est présente dans votre code Svelte, l'erreur fournie aura une propriété supplémentaire frame mettant en lumière la position de l'erreur.

Assurez-vous que handleError ne déclenche jamais d'erreur.

Hooks universels

Vous pouvez ajouter la fonction suivante à votre fichier src/hooks.js. Les hooks universels sont exécutés à la fois sur le client et sur le serveur (à ne pas confondre avec les hooks partagés, qui sont spécifiques à chaque environnement).

reroute

Cette fonction est exécutée avant handle et vous permet de changer la manière dont les URLs sont transformées en routes. Le paramètre renvoyé (dont la valeur par défaut est 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.js
ts
/** @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];
}
}
src/hooks.ts
ts
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 extrait depuis le chemin renvoyé.

Le fait d'utiliser reroute ne change pas le contenu de la barre d'adresse, ou la valeur de event.url.

Sur le même sujet

précédent Routing avancé
suivant Erreurs