为何Sveltekit的handleFetch仅在全页重载时能正常拦截请求?
问题原因
你踩了SvelteKit handleFetch 的一个常见坑:这个钩子只在服务器端生效,完全管不到浏览器端发起的请求。
- 全页刷新(F5、直接输URL)时,页面是SSR渲染,
page.ts里的fetch是在服务器端触发的,所以handleFetch能拦截并加上Authorization头,请求正常。 - 用
a href、redirect这类客户端导航时,页面是在浏览器端渲染的,page.ts里的fetch是浏览器直接发的,这时候hooks.server.ts里的handleFetch根本不会运行,自然没加请求头,导致接口报错。
你看到的控制台日志是服务器端的输出,对应的可能是之前全页刷新的请求,不是客户端导航时的那个请求。
解决方案
需要同时处理服务器端和客户端的 fetch 请求,下面给两种可行方案:
方案一:封装全局自定义 fetch 函数
创建一个通用的 fetch 封装,自动在客户端和服务器端添加请求头:
- 在
src/lib下新建fetch.ts:
import { browser } from '$app/environment'; export async function customFetch(url: string, options: RequestInit = {}) { const headers = new Headers(options.headers); // 只给目标域名的请求加Authorization头 if (url.startsWith('http://')) { let accessToken; if (browser) { // 客户端从document.cookie中取token accessToken = document.cookie.split('; ') .find(row => row.startsWith('access_token='))?.split('=')[1]; } // 服务器端的请求已经由handleFetch处理,这里不用重复操作 if (accessToken) { headers.set('Authorization', `Bearer ${accessToken}`); } } return fetch(url, { ...options, headers }); }
- 在
page.ts里替换成这个自定义函数:
import type { PageLoad } from './$types'; import { customFetch } from '$lib/fetch'; export const load: PageLoad = async () => { const response = await customFetch('http://127.0.0.1:8080/api/v1/fetch_accounts', { method: 'GET', credentials: 'include' }); const content = await response.json(); return { content }; };
方案二:用客户端钩子拦截 fetch
创建 hooks.client.ts,专门处理浏览器端的 fetch 请求:
export async function handleFetch({ request, fetch }) { if (request.url.startsWith('http://')) { // 从客户端cookie中取token const accessToken = document.cookie.split('; ') .find(row => row.startsWith('access_token='))?.split('=')[1]; if (accessToken) { request.headers.set('Authorization', `Bearer ${accessToken}`); } } return fetch(request); }
这个钩子会拦截所有浏览器端发起的 fetch 请求,自动加上请求头,不需要修改 page.ts 里的代码。
额外注意点
- 确保你的
access_tokencookie没有设置HttpOnly属性(如果要在客户端读取的话),如果是HttpOnly,客户端拿不到,只能依赖服务器端的handleFetch处理SSR请求,同时后端要配置允许cookie自动携带。 - 服务器端的
handleFetch逻辑可以保留,继续处理SSR时的请求。
内容的提问来源于stack exchange,提问作者MichlF
相关产品推荐
相关产品推荐

