Next.js 13 App Router中客户端Token的最优存储方案
Next.js 13.5 App Router 下 Token 存储最优方案
针对你遇到的 SSR 场景 Token 存储、登录跳转逻辑混乱的问题,最优方案是HttpOnly Cookie + React Context组合,既解决服务端渲染时的 Token 读取问题,又能在客户端优雅管理状态,同时满足安全和产品需求。
核心方案:HttpOnly Cookie 存储 Token
为什么选 Cookie?
- 服务端渲染时,Next.js App Router 可以通过
cookies()函数直接读取 Cookie,完全规避 localStorage 必须等客户端 hydrate 才能获取的问题,从根源解决首次渲染依赖 Token 的请求失败问题。 - 设置为 HttpOnly、Secure、SameSite=Strict 的 Cookie 能有效防止 XSS 攻击,比 localStorage 更安全。
具体实现步骤
登录成功后设置 Cookie
登录获取外部服务 Token 后,通过后端接口(或外部服务回调逻辑)将 Token 写入 HttpOnly Cookie:// 示例:后端设置Cookie的逻辑(Node.js/Express) res.cookie('auth_token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000, // 有效期7天 path: '/' });服务端组件处理登录校验与重定向
根页面(app/page.tsx)采用服务端组件,直接读取 Cookie 验证 Token 有效性,未登录则直接重定向到/login,避免客户端渲染闪烁和代码混乱:// app/page.tsx import { cookies, redirect } from 'next/headers'; import { verifyToken } from '@/lib/auth'; // 自定义Token验证函数 export default async function Home() { const token = cookies().get('auth_token')?.value; const isValid = await verifyToken(token); // 调用接口或解析JWT验证Token if (!token || !isValid) { redirect('/login'); } // 渲染认证后的页面内容 return <div>认证页内容</div>; }GraphQL 请求的 Token 传递
- 服务端组件:直接从
cookies()获取 Token,加入 GraphQL 请求头:// 服务端组件中发起GraphQL请求 const token = cookies().get('auth_token')?.value; const response = await fetch(process.env.GRAPHQL_API, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ query: '...' }) }); - 客户端组件:通过 React Context 获取 Token(下文会讲),加入请求头即可。
- 服务端组件:直接从
客户端状态管理:React Context 配合 Cookie
为了避免客户端重复解析 Cookie,用 React Context 统一管理 Token 状态,同时处理登出等逻辑:
// app/providers/AuthProvider.tsx 'use client'; import { createContext, useContext, useEffect, useState } from 'react'; interface AuthContextType { token: string | null; logout: () => void; } const AuthContext = createContext<AuthContextType | undefined>(undefined); export function AuthProvider({ children }: { children: React.ReactNode }) { const [token, setToken] = useState<string | null>(null); // 客户端初始化时从Cookie读取Token useEffect(() => { const tokenCookie = document.cookie .split('; ') .find(row => row.startsWith('auth_token=')) ?.split('=')[1]; if (tokenCookie) { setToken(tokenCookie); } }, []); // 登出:清除Cookie和Context状态 const logout = () => { setToken(null); document.cookie = 'auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; // 可选:跳转到登录页 window.location.href = '/login'; }; return ( <AuthContext.Provider value={{ token, logout }}> {children} </AuthContext.Provider> ); } // 自定义Hook,方便组件调用 export function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error('useAuth must be used within AuthProvider'); return context; }
然后在根布局中包裹所有组件:
// app/layout.tsx import { AuthProvider } from './providers/AuthProvider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="zh-CN"> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ); }
为什么不推荐 localStorage?
- 服务端渲染阶段无法访问 localStorage,必须在
useEffect中异步获取,导致首次渲染时依赖 Token 的组件请求失败,需要额外的条件渲染或重定向逻辑,代码冗余且易出问题。 - localStorage 存储的 Token 容易被 XSS 攻击窃取,安全性远低于 HttpOnly Cookie。
额外优化建议
- Token 自动刷新:在客户端通过请求拦截器检测 Token 过期时间,到期前自动调用刷新接口,更新 Cookie 和 Context 中的 Token。
- 服务端 Token 缓存:如果 Token 验证逻辑耗时,可以在服务端组件中缓存验证结果,提升页面渲染性能。
内容的提问来源于stack exchange,提问作者Caleb Jay
相关产品推荐
相关产品推荐

