Next.js 13 App目录服务端组件401错误下的Token刷新处理方案
服务端组件中401错误的处理方案
首先明确:不是必须被迫使用客户端组件,服务端场景下有几种针对性的处理思路,可根据业务场景选择:
1. 服务端直接重定向到登录页
如果你的应用依赖会话式身份验证(比如用HTTP-only cookie存储refresh token),服务端组件请求API遇到401时,可直接发起重定向跳转至登录页。这种方式适用于refresh token也已过期、需要用户重新验证身份的场景。
示例代码(Next.js服务端组件):
async function ServerComponent() { try { const accessToken = cookies().get('accessToken')?.value; const response = await fetch('https://your-api.com/protected-resource', { headers: { Authorization: `Bearer ${accessToken}` }, cache: 'no-store' }); if (!response.ok) { if (response.status === 401) { // 服务端触发重定向 redirect('/login'); } throw new Error('资源请求失败'); } const data = await response.json(); return <div>{data.content}</div>; } catch (err) { return <div>加载失败,请稍后重试</div>; } }
2. 服务端中间件统一处理Token刷新
将401拦截和token刷新逻辑放在服务端中间件(或API路由)中,而非服务端组件内。这种方式能让组件专注于渲染逻辑,权限处理逻辑全局统一。
示例代码(Next.js中间件):
export async function middleware(request: NextRequest) { const accessToken = request.cookies.get('accessToken')?.value; const refreshToken = request.cookies.get('refreshToken')?.value; if (!accessToken) { return NextResponse.redirect(new URL('/login', request.url)); } // 校验当前accessToken有效性 const authCheckResponse = await fetch('https://your-api.com/check-auth', { headers: { Authorization: `Bearer ${accessToken}` } }); if (authCheckResponse.status === 401 && refreshToken) { // 调用刷新token接口 const refreshResponse = await fetch('https://your-api.com/refresh-token', { method: 'POST', body: JSON.stringify({ refreshToken }) }); if (refreshResponse.ok) { const { newAccessToken } = await refreshResponse.json(); // 更新cookie中的accessToken const newResponse = NextResponse.next(); newResponse.cookies.set('accessToken', newAccessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production' }); return newResponse; } else { // refreshToken失效,强制跳转登录 return NextResponse.redirect(new URL('/login', request.url)); } } return NextResponse.next(); } export const config = { matcher: ['/protected/:path*'] };
3. 局部降级为客户端组件(特定场景)
如果业务需要保留当前页面状态、不能直接跳转到登录页,可将需要处理401的动态内容抽离为客户端组件,服务端组件负责渲染静态、无需权限校验的部分。
示例代码:
// 父级服务端组件 async function ParentServerComponent() { // 渲染静态公共内容 return ( <div> <h1>用户中心</h1> {/* 权限相关内容交由客户端组件处理 */} <UserProfile /> </div> ); } // 客户端组件 'use client' 'use client'; import { useEffect, useState } from 'react'; function UserProfile() { const [profile, setProfile] = useState(null); const [error, setError] = useState(''); useEffect(() => { async function fetchProfile() { let accessToken = localStorage.getItem('accessToken'); let response = await fetch('https://your-api.com/user-profile', { headers: { Authorization: `Bearer ${accessToken}` } }); if (response.status === 401) { const refreshToken = localStorage.getItem('refreshToken'); const refreshRes = await fetch('https://your-api.com/refresh-token', { method: 'POST', body: JSON.stringify({ refreshToken }) }); if (refreshRes.ok) { const { newAccessToken } = await refreshRes.json(); localStorage.setItem('accessToken', newAccessToken); // 重新发起请求 response = await fetch('https://your-api.com/user-profile', { headers: { Authorization: `Bearer ${newAccessToken}` } }); } else { setError('登录已过期,请重新登录'); window.location.href = '/login'; return; } } const data = await response.json(); setProfile(data); } fetchProfile(); }, []); if (error) return <div className="error">{error}</div>; if (!profile) return <div>加载中...</div>; return <div>用户名:{profile.name}</div>; }
总结
服务端组件遇到401时,优先选择服务端重定向或中间件统一处理,这两种方式更贴合服务端渲染的特性,避免权限逻辑分散。只有在需要保留页面状态、必须前端处理的场景下,再考虑局部使用客户端组件。
内容的提问来源于stack exchange,提问作者Chanwoo Park
相关产品推荐
相关产品推荐

