You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写适用于服务端与客户端的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:21:03