跳到主要內容

進階

Hooks

在 GitHub 上編輯此頁面

「Hooks」是您宣告的應用程式範圍函式,SvelteKit 會在特定事件發生時呼叫,讓您能精細地控制架構的行為。

有三個 hooks 檔案,全部都是選用的

  • src/hooks.server.js — 您的應用程式伺服器 hooks
  • src/hooks.client.js — 您的應用程式用戶端 hooks
  • src/hooks.js — 您的應用程式 hooks,會在用戶端和伺服器上執行

這些模組中的程式碼會在應用程式啟動時執行,因此可以用來初始化資料庫用戶端等等。

您可以使用 config.kit.files.hooks 來設定這些檔案的位置。

伺服器 hooks

下列 hooks 可以新增到 src/hooks.server.js

handle

這個函式會在 SvelteKit 伺服器每次收到 請求 時執行,無論是在應用程式執行期間,或是在 預先渲染 期間,並決定 回應。它會收到一個表示請求的 event 物件,以及一個稱為 resolve 的函式,用來渲染路由並產生一個 Response。這讓您可以修改回應標頭或本文,或完全繞過 SvelteKit(例如,用來以程式化方式實作路由)。

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('custom response');
}
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('custom response');
}
const response = await resolve(event);
return response;
};

SvelteKit 不會 處理靜態資源的請求,其中包括已經預先渲染的頁面。

如果未實作,則預設為 ({ event, resolve }) => resolve(event)。若要將自訂資料新增到請求(會傳遞給 +server.js 和伺服器 load 函式中的處理常式),請填入 event.locals 物件,如下所示。

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', 'potato');
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', 'potato');
return response;
};

您可以定義多個 handle 函式,並使用 sequence 輔助函式 來執行它們。

resolve 也支援第二個選用參數,讓您可以更進一步地控制回應的渲染方式。該參數是一個物件,可以包含下列欄位

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — 對 HTML 套用自訂轉換。如果 done 為 true,則為最後一個區塊。區塊不保證是格式良好的 HTML(例如,它們可能包含元素的開啟標籤,但不包含其關閉標籤),但它們一定會在合理的界線上分割,例如 %sveltekit.head% 或版面/頁面元件。
  • filterSerializedResponseHeaders(name: string, value: string): boolean — 決定當 load 函式使用 fetch 載入資源時,哪些標頭應包含在序列化回應中。預設情況下,不會包含任何標頭。
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean — 決定應將哪些檔案新增至 <head> 標籤以預先載入。此方法會在建置時間找到的每個檔案中呼叫,同時建構程式碼區塊 — 因此,如果你在 +page.svelte 中有 import './styles.css,則在造訪該頁面時,preload 會使用解析後的路徑呼叫該 CSS 檔案。請注意,在開發模式中,preload 不會 被呼叫,因為它依賴於建置時間發生的分析。預先載入可以透過提早下載資源來改善效能,但如果過度下載不必要的資源,也可能會造成損害。預設情況下,jscss 檔案將會預先載入。目前並未預先載入 asset 檔案,但我們可能會在評估回饋後稍後新增此功能。
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;
};

請注意,resolve(...) 永遠不會擲回錯誤,它會永遠傳回具有適當狀態碼的 Promise<Response>。如果在 handle 期間其他地方擲回錯誤,則會將其視為致命錯誤,而 SvelteKit 會回應錯誤的 JSON 表示形式或後備錯誤頁面 — 可以透過 src/error.html 自訂 — 視 Accept 標頭而定。你可以 在此 閱讀更多關於錯誤處理的資訊。

handleFetch

此函式允許你修改 (或取代) 在伺服器上 (或在預先渲染期間) 執行 loadaction 函式中發生的 fetch 要求。

例如,當使用者對應頁面執行用戶端導覽時,你的 load 函式可能會對公共 URL (例如 https://api.yourapp.com) 發出要求,但在 SSR 期間,直接存取 API 可能比較合理 (繞過它和公共網路之間的任何代理伺服器和負載平衡器)。

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 the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'https://127.0.0.1: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 the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'https://127.0.0.1:9999/'),
request,
);
}
return fetch(request);
};

憑證

對於同源要求,除非將 credentials 選項設定為 "omit",否則 SvelteKit 的 fetch 實作會轉發 cookieauthorization 標頭。

對於跨來源請求,如果請求 URL 屬於應用程式的子網域,則會包含 cookie — 例如,如果您的應用程式位於 my-domain.com,而您的 API 位於 api.my-domain.com,則 cookie 會包含在請求中。

如果您的應用程式和 API 位於同層子網域 — 例如 www.my-domain.comapi.my-domain.com — 則屬於共同父網域(例如 my-domain.com)的 cookie 不會 包含在內,因為 SvelteKit 無法得知 cookie 屬於哪個網域。在這些情況下,您需要使用 handleFetch 手動包含 cookie

src/hooks.server.js
ts
/** @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'));
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.my-domain.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);
};

共用 hook

以下內容可以新增到 src/hooks.server.js src/hooks.client.js

handleError

如果在載入或渲染期間發生 意外錯誤,則會呼叫此函式,並傳入 erroreventstatus 程式碼和 message。這允許執行兩件事

  • 您可以記錄錯誤
  • 您可以產生錯誤的客製化表示形式,以安全的方式顯示給使用者,並省略敏感的詳細資料,例如訊息和堆疊追蹤。回傳值預設為 { message },會變成 $page.error 的值。

對於從您的程式碼(或您的程式碼呼叫的函式庫程式碼)引發的錯誤,狀態會是 500,而訊息會是「內部錯誤」。雖然 error.message 可能包含不應揭露給使用者的敏感資訊,但 message 是安全的(儘管對一般使用者來說沒有意義)。

若要以類型安全的方式將更多資訊新增到 $page.error 物件中,您可以透過宣告 App.Error 介面(必須包含 message: string,以確保合理的後備行為)來自訂預期的形狀。這允許您 — 例如 — 附加追蹤 ID,讓使用者在與您的技術支援人員通信時引用

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();
// example integration with 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: 'Whoops!',
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();
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId, status },
});
return {
message: 'Whoops!',
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();
// example integration with 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: 'Whoops!',
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();
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId, status },
});
return {
message: 'Whoops!',
errorId,
};
};

src/hooks.client.js 中,handleError 的類型是 HandleClientError,而不是 HandleServerError,而 eventNavigationEvent,而不是 RequestEvent

此函式不會針對預期錯誤(使用從 @sveltejs/kit 匯入的 error 函式引發的錯誤)呼叫。

在開發期間,如果因為 Svelte 程式碼中的語法錯誤而發生錯誤,則傳入的錯誤會附加上一個 frame 屬性,以突顯錯誤的位置。

請確定 handleError 絕不會 引發錯誤

通用掛勾

以下內容可以新增到 src/hooks.js。通用掛勾可以在伺服器和用戶端上執行(不要與環境特定的共用掛勾混淆)。

重新導向

此函式在 handle 之前執行,允許您變更 URL 轉換為路由的方式。傳回的路徑名稱(預設為 url.pathname)用於選擇路由及其參數。

例如,您可能有一個 src/routes/[[lang]]/about/+page.svelte 頁面,可以透過 /en/about/de/ueber-uns/fr/a-propos 存取。您可以使用 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];
}
};

lang 參數會從傳回的路徑名稱正確衍生而來。

使用 reroute 不會 變更瀏覽器網址列的內容,或 event.url 的值。

進一步閱讀

上一個 進階路由
下一個 錯誤