Next.js 14服务端渲染时AccessToken过期的RefreshToken刷新方案问询
Next.js 14 App Router 服务端渲染 Token 过期刷新方案
针对你遇到的服务端渲染时 AccessToken 过期、但 RefreshToken Cookie 路径限制导致无法在服务端刷新的问题,以下是几种落地性强的解决方案:
一、优先方案:调整 RefreshToken Cookie 路径(需后端配合)
如果可以修改 .NET API 的 Cookie 设置,直接将 RefreshToken 的路径改为根路径 /,这是最简洁的解决方式:
// .NET API 端设置 Cookie 的代码 Response.Cookies.Append("RefreshToken", refreshTokenValue, new CookieOptions { Path = "/", // 修改为根路径 HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTime.UtcNow.AddDays(7) });
修改后,Next.js 服务端组件可以直接从请求 Cookie 中获取 RefreshToken,调用 /auth/refresh-token 接口刷新 AccessToken,全程在服务端完成,无需客户端介入,体验最优。
二、服务端预校验 + 客户端刷新兜底
如果无法调整 Cookie 路径,可在服务端组件中先做 Token 有效性校验,失效时返回客户端组件完成刷新,再触发服务端重渲染:
1. 服务端组件(个人资料页)
async function ProfilePage() { const accessToken = cookies().get('AccessToken')?.value; let isTokenValid = false; // 调用轻量的 Token 校验接口(避免请求大体积数据失败) try { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/validate-token`, { headers: { Authorization: `Bearer ${accessToken}` } }); isTokenValid = res.ok; } catch (err) { isTokenValid = false; } // Token 无效时返回客户端刷新组件 if (!isTokenValid) { return <ClientTokenRefresh />; } // Token 有效时正常请求用户数据并渲染 const userProfile = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/profile`, { headers: { Authorization: `Bearer ${accessToken}` } }).then(res => res.json()); return <ProfileContent data={userProfile} />; }
2. 客户端刷新组件(Client Component)
'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import axios from 'axios'; export default function ClientTokenRefresh() { const router = useRouter(); const [refreshFailed, setRefreshFailed] = useState(false); useEffect(() => { async function refreshAndReload() { try { // 复用现有 Axios 中间件完成 Token 刷新 await axios.post('/auth/refresh-token'); // 刷新成功后触发服务端页面重渲染 router.refresh(); } catch (err) { setRefreshFailed(true); } } refreshAndReload(); }, [router]); if (refreshFailed) { return ( <div> 登录状态已过期,请重新登录 <button onClick={() => router.push('/auth/login')}>去登录</button> </div> ); } return <div>正在加载...</div>; }
三、全局 Middleware 拦截方案(优化跳转体验)
通过 Next.js Middleware 拦截所有需鉴权的页面请求,Token 失效时跳转到客户端刷新页面,完成后自动返回原页面:
1. Middleware 代码
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { const accessToken = request.cookies.get('AccessToken')?.value; const path = request.nextUrl.pathname; // 排除无需鉴权的路径(如登录、刷新页) if (path.startsWith('/auth')) { return NextResponse.next(); } // 校验 Token 有效性 let isTokenValid = false; try { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/validate-token`, { headers: { Authorization: `Bearer ${accessToken}` } }); isTokenValid = res.ok; } catch (err) { isTokenValid = false; } // Token 失效时跳转到刷新页,携带原路径参数 if (!isTokenValid) { const redirectUrl = new URL('/auth/refresh-token', request.url); redirectUrl.searchParams.set('redirect', path); return NextResponse.redirect(redirectUrl); } return NextResponse.next(); } // 指定需要鉴权的页面路径 export const config = { matcher: ['/profile/:path*', '/dashboard/:path*'], };
2. 客户端刷新页代码
'use client'; import { useEffect } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import axios from 'axios'; export default function RefreshTokenPage() { const router = useRouter(); const searchParams = useSearchParams(); const redirectTo = searchParams.get('redirect') || '/'; useEffect(() => { async function refreshAndRedirect() { try { await axios.post('/auth/refresh-token'); router.push(redirectTo); } catch (err) { router.push('/auth/login'); } } refreshAndRedirect(); }, [router, redirectTo]); return <div>正在刷新登录状态...</div>; }
内容的提问来源于stack exchange,提问作者Mil
相关产品推荐
相关产品推荐

