NextJS服务端组件中刷新Access Token并写入Cookie的问题解决
问题描述
我用NextJS做前端、FastAPI做后端、Clerk实现认证功能,需要从Clerk获取JWT Access Token并存入Cookie做缓存。写了一个Server Action,逻辑如下:
- 尝试从Cookie中获取
access_token - 若Token不存在或已过期,通过Clerk的
getToken()获取新Token并写入Cookie
这个Action在客户端组件调用时完全正常,但在服务端组件调用时直接报错:
Error: Cookies can only be modified in a Server Action or Route Handler.
相关代码
Server Action 代码
"use server"; import jwt from "jsonwebtoken"; import { cookies } from "next/headers"; import { auth } from "@clerk/nextjs/server"; import { CLERK_PEM_PUBLIC_KEY } from "@/config"; /** * Get the access token from cookies. * * If the token is not found or expired, get a new token from Clerk. * * @returns {Promise<{ token: string, isNewToken: boolean }>} */ export const getAccessTokenAction = async () => { let accessToken = cookies().get("access_token"); if (accessToken) { try { await jwt.verify(accessToken.value, CLERK_PEM_PUBLIC_KEY); return { token: accessToken.value, isNewToken: false, }; } catch (error) { if (error instanceof jwt.TokenExpiredError) { // Token expired, do nothing and just break the if block and get a new token } else throw error; } } const { getToken } = auth(); const newAccessToken = await getToken(); if (!newAccessToken) throw new Error("Failed to get access token"); cookies().set("access_token", newAccessToken); return { token: newAccessToken, isNewToken: true, }; };
客户端组件调用(正常工作)
"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { getAccessTokenAction } from "../_actions/get-access-token"; const ClientPage = () => { const [token, setToken] = useState<string | undefined>(); const [isNewToken, setIsNewToken] = useState<boolean | undefined>(); const handleClick = () => { getAccessTokenAction().then(({ token, isNewToken }) => { setToken(token); setIsNewToken(isNewToken); }); }; return ( <div> <Button onClick={handleClick}>点击获取Access Token</Button> <div>{token}</div> <div>{isNewToken}</div> </div> ); }; export default ClientPage;
服务端组件调用(报错)
import { getAccessTokenAction } from "../_actions/get-access-token"; const ServerPage = async () => { const { token, isNewToken } = await getAccessTokenAction(); return ( <div> <div>{isNewToken}</div> <div>{token}</div> </div> ); }; export default ServerPage;
问题原因
NextJS的规则是:服务端组件在服务器渲染阶段执行时,HTTP响应已经开始构建,此时无法修改响应头(Cookie是通过响应头设置的)。而客户端调用Server Action时,是通过独立的POST请求触发的,属于完整的请求响应流程,所以能正常修改Cookie。
解决方案
方案1:用客户端组件触发Token获取
在服务端组件中嵌入一个客户端组件,由它来触发Server Action完成Token获取和Cookie设置:
// 客户端组件:TokenFetcher.tsx "use client"; import { useEffect } from "react"; import { getAccessTokenAction } from "../_actions/get-access-token"; export const TokenFetcher = () => { useEffect(() => { // 组件挂载时自动获取并缓存Token getAccessTokenAction(); }, []); return null; };
然后在服务端组件中引入:
import { cookies } from "next/headers"; import { TokenFetcher } from "./TokenFetcher"; const ServerPage = async () => { // 这里可以直接读取已缓存的Token const cachedToken = cookies().get("access_token")?.value; return ( <div> <TokenFetcher /> <div>{cachedToken ? "Token已缓存" : "Token正在获取"}</div> <div>{cachedToken}</div> </div> ); }; export default ServerPage;
方案2:将服务端组件改为动态客户端渲染
用NextJS的dynamic导入组件并关闭SSR,让组件在客户端执行,这样就能正常调用Server Action:
// 页面入口文件 import dynamic from "next/dynamic"; // 关闭SSR,组件会在客户端渲染 const DynamicServerPage = dynamic(() => import("./ServerPage"), { ssr: false }); export default DynamicServerPage;
原ServerPage代码保持不变即可。
方案3:拆分Token获取与Cookie设置逻辑
如果必须在服务端渲染时获取Token,可以只在服务端组件中读取Token(不设置Cookie),再单独写一个Route Handler来处理Cookie缓存,由客户端调用这个接口完成缓存操作。
内容的提问来源于stack exchange,提问作者steveflyer
相关产品推荐
相关产品推荐

