如何编写适用于服务端与客户端的SvelteKit Fetch中间件或拦截器?
在SvelteKit中实现服务端与客户端通用的拦截器/中间件
你已经知道用hooks.server.ts处理服务端请求拦截,下面就来说说怎么覆盖客户端场景,以及如何封装通用逻辑。
一、服务端拦截回顾(快速过一遍)
先确认下你已有的服务端逻辑,hooks.server.ts里的handle函数是服务端所有请求的入口,不管是页面路由还是API请求都能拦截:
// hooks.server.ts export async function handle({ event, resolve }) { // 请求前拦截:比如打日志、鉴权 console.log(`服务端收到请求: ${event.url.pathname}`); // 继续处理请求 const response = await resolve(event); // 响应后拦截:比如统一添加响应头 response.headers.set('X-Custom-Header', 'sveltekit-middleware'); return response; }
二、客户端拦截的两种场景
客户端的拦截主要分路由导航拦截和API请求拦截,分别对应不同的实现方式。
1. 客户端路由导航拦截
用hooks.client.ts的handle函数,专门处理客户端的路由跳转(比如用户点击SvelteKit的<a>链接、调用$navigate):
// hooks.client.ts export async function handle({ event, resolve }) { // 跳转前拦截:比如检查登录状态、显示加载动画 console.log(`客户端即将跳转: ${event.url.pathname}`); // 允许跳转,继续处理 const response = await resolve(event); // 跳转完成后拦截:比如隐藏加载动画 console.log(`客户端跳转完成: ${event.url.pathname}`); return response; }
如果需要拦截未登录用户访问私有路由,直接在这里返回跳转响应就行:
// hooks.client.ts 示例:未登录跳转登录页 export async function handle({ event, resolve }) { const isLoggedIn = localStorage.getItem('token') !== null; const isPrivateRoute = event.url.pathname.startsWith('/dashboard'); if (isPrivateRoute && !isLoggedIn) { return new Response(null, { status: 302, headers: { Location: '/login' } }); } return resolve(event); }
2. 客户端API请求拦截
如果是拦截前端发起的API请求(比如fetch),最直接的方式是封装一个自己的请求函数,代替原生fetch:
// lib/api.ts export async function apiFetch(url: string, options: RequestInit = {}) { // 请求前拦截:统一添加token、设置headers const defaultHeaders = { 'Content-Type': 'application/json', ...(localStorage.getItem('token') && { Authorization: `Bearer ${localStorage.getItem('token')}` }) }; console.log(`客户端API请求: ${url}`); try { const response = await fetch(url, { ...options, headers: { ...defaultHeaders, ...options.headers } }); // 响应后拦截:统一处理错误状态 if (!response.ok) { const error = await response.json(); throw new Error(error.message || `请求失败: ${response.status}`); } console.log(`客户端API响应成功: ${url}`); return response.json(); } catch (err) { // 全局错误处理:比如弹提示、跳登录页 console.error('API请求出错:', err); throw err; } }
之后在客户端组件里,都用这个apiFetch来发请求,就能统一拦截所有前端API调用了。
三、通用逻辑封装,两端复用
如果有一些逻辑(比如鉴权规则、日志格式)是服务端和客户端都要用的,抽成单独的工具函数就行:
// lib/middleware-utils.ts // 通用日志函数 export function logRequest(path: string, isServer: boolean) { console.log(`[${isServer ? '服务端' : '客户端'}] 请求路径: ${path}`); } // 通用鉴权校验 export function isAuthorized(token: string | null) { // 这里写你的鉴权逻辑,比如验证token格式、有效期 return token && token.length > 10; }
然后在服务端的hooks.server.ts里调用:
// hooks.server.ts import { logRequest, isAuthorized } from '$lib/middleware-utils'; export async function handle({ event, resolve }) { const token = event.cookies.get('token'); logRequest(event.url.pathname, true); if (!isAuthorized(token)) { return new Response('未授权', { status: 401 }); } return resolve(event); }
客户端的hooks.client.ts里也调用:
// hooks.client.ts import { logRequest, isAuthorized } from '$lib/middleware-utils'; export async function handle({ event, resolve }) { const token = localStorage.getItem('token'); logRequest(event.url.pathname, false); if (!isAuthorized(token) && event.url.pathname.startsWith('/dashboard')) { return new Response(null, { status: 302, headers: { Location: '/login' } }); } return resolve(event); }
四、几个注意点
hooks.server.ts只在服务端运行,别在这里用客户端API(比如localStorage);hooks.client.ts只在客户端运行,别用服务端API(比如event.cookies)。- 服务端的
handle能拦截所有进入服务端的请求(包括前端发的API请求),如果想单独拦截API路由,也可以在+server.ts里写逻辑,或者用hooks.server.ts里的event.url.pathname判断路径。 - 如果是页面的
load函数需要拦截逻辑,服务端的+page.server.ts和客户端的+page.ts可以共享同一个工具函数,实现逻辑复用。
内容的提问来源于stack exchange,提问作者Blagoje
相关产品推荐
相关产品推荐

