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

Next.js 13 App Router中客户端Token的最优存储方案

Next.js 13.5 App Router 下 Token 存储最优方案

针对你遇到的 SSR 场景 Token 存储、登录跳转逻辑混乱的问题,最优方案是HttpOnly Cookie + React Context组合,既解决服务端渲染时的 Token 读取问题,又能在客户端优雅管理状态,同时满足安全和产品需求。

为什么选 Cookie?

  • 服务端渲染时,Next.js App Router 可以通过 cookies() 函数直接读取 Cookie,完全规避 localStorage 必须等客户端 hydrate 才能获取的问题,从根源解决首次渲染依赖 Token 的请求失败问题。
  • 设置为 HttpOnly、Secure、SameSite=Strict 的 Cookie 能有效防止 XSS 攻击,比 localStorage 更安全。

具体实现步骤

  1. 登录成功后设置 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: '/'
    });
    
  2. 服务端组件处理登录校验与重定向
    根页面(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>;
    }
    
  3. 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(下文会讲),加入请求头即可。

为了避免客户端重复解析 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 02:44:51