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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 21:05:03