Concepts avancés
Hooks
Éditer cette page sur GithubLes "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 applicationsrc/hooks.client.js
— les hooks client de votre applicationsrc/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 serveurpermalink
Les hooks suivants peuvent être ajoutés au fichier src/hooks.server.js
:
handlepermalink
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).
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {if (event .url .pathname .startsWith ('/custom')) {return newResponse ('réponse personnalisée');}constresponse = awaitresolve (event );returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {if (event .url .pathname .startsWith ('/custom')) {return newResponse ('réponse personnalisée');}constresponse = awaitresolve (event );returnresponse ;};
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.
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {event .locals .user = awaitgetUserInformation (event .cookies .get ('sessionid'));constresponse = awaitresolve (event );response .headers .set ('x-custom-header', 'patate');returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {event .locals .user = awaitgetUserInformation (event .cookies .get ('sessionid'));constresponse = awaitresolve (event );response .headers .set ('x-custom-header', 'patate');returnresponse ;};
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. Sidone
vauttrue
, 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 fonctionload
charge une ressource avecfetch
. 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 exempleimport './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 fichiersjs
etcss
seront préchargés. Les fichiersasset
ne sont pas préchargés du tout actuellement, mais pourront l'être plus tard après analyse des retours d'expérience.
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {constresponse = awaitresolve (event , {transformPageChunk : ({html }) =>html .replace ('old', 'new'),filterSerializedResponseHeaders : (name ) =>name .startsWith ('x-'),preload : ({type ,path }) =>type === 'js' ||path .includes ('/important/')});returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {constresponse = awaitresolve (event , {transformPageChunk : ({html }) =>html .replace ('old', 'new'),filterSerializedResponseHeaders : (name ) =>name .startsWith ('x-'),preload : ({type ,path }) =>type === 'js' ||path .includes ('/important/'),});returnresponse ;};
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.
handleFetchpermalink
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).
ts
/** @type {import('@sveltejs/kit').HandleFetch} */export async functionhandleFetch ({request ,fetch }) {if (request .url .startsWith ('https://api.yourapp.com/')) {// clone la requête originale, mais en change l'URLrequest = newRequest (request .url .replace ('https://api.yourapp.com/', 'http://localhost:9999/'),request );}returnfetch (request );}
ts
import type {HandleFetch } from '@sveltejs/kit';export consthandleFetch :HandleFetch = async ({request ,fetch }) => {if (request .url .startsWith ('https://api.yourapp.com/')) {// clone la requête originale, mais en change l'URLrequest = newRequest (request .url .replace ('https://api.yourapp.com/', 'http://localhost:9999/'),request ,);}returnfetch (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
:
ts
/** @type {import('@sveltejs/kit').HandleFetch} */export async functionhandleFetch ({event ,request ,fetch }) {if (request .url .startsWith ('https://api.mon-domaine.com/')) {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'.request .headers .set ('cookie',event .request .headers .get ('cookie'));}returnfetch (request );}
ts
import type {HandleFetch } from '@sveltejs/kit';export consthandleFetch :HandleFetch = async ({event ,request ,fetch }) => {if (request .url .startsWith ('https://api.mon-domaine.com/')) {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'.request .headers .set ('cookie',event .request .headers .get ('cookie'));}returnfetch (request );};
Hooks partagéspermalink
Les fonctions suivantes peuvent être ajoutées aux fichiers src/hooks.server.js
et src/hooks.client.js
:
handleErrorpermalink
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 :
ts
declareglobal {namespaceApp {interfaceError {message : string;errorId : string;}}}export {};
ts
import * asSentry from '@sentry/sveltekit';Sentry .init ({/*...*/})/** @type {import('@sveltejs/kit').HandleServerError} */export async functionhandleError ({error ,event ,status ,message }) {consterrorId =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 };}
ts
import * asSentry from '@sentry/sveltekit';import type {HandleServerError } from '@sveltejs/kit';Sentry .init ({/*...*/});export consthandleError :HandleServerError = async ({error ,event ,status ,message }) => {consterrorId =crypto .randomUUID ();// exemple d'intégration utilisant https://sentry.io/Sentry .captureException (error , {extra : {event ,errorId ,status },});return {message : 'Oups !',errorId ,};};
ts
import * asSentry from '@sentry/sveltekit';Sentry .init ({/*...*/})/** @type {import('@sveltejs/kit').HandleClientError} */export async functionhandleError ({error ,event ,status ,message }) {consterrorId =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 };}
ts
import * asSentry from '@sentry/sveltekit';import type {HandleClientError } from '@sveltejs/kit';Sentry .init ({/*...*/});export consthandleError :HandleClientError = async ({error ,event ,status ,message }) => {consterrorId =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 dehandleError
estHandleClientError
plutôt queHandleServerError
, etevent
est unNavigationEvent
plutôt qu'unRequestEvent
.
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 universelspermalink
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).
reroutepermalink
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
:
ts
/** @type {Record<string, string>} */consttranslated = {'/en/about': '/en/about','/de/ueber-uns': '/de/about','/fr/a-propos': '/fr/about',};/** @type {import('@sveltejs/kit').Reroute} */export functionreroute ({url }) {if (url .pathname intranslated ) {returntranslated [url .pathname ];}}
ts
import type {Reroute } from '@sveltejs/kit';consttranslated :Record <string, string> = {'/en/about': '/en/about','/de/ueber-uns': '/de/about','/fr/a-propos': '/fr/about',};export constreroute :Reroute = ({url }) => {if (url .pathname intranslated ) {returntranslated [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
.