Next.js 13:如何将内存中的Access Token传递给Server Component?
我把Refresh Token存在Cookie中,Access Token则保存在内存的Zustand状态仓库里,客户端组件中获取Access Token的代码如下:
"use client"; import Image from "next/image"; import { motion } from "framer-motion"; import { AxiosResponse } from "axios"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useStore } from "@/globalState"; import { ServerResponse, userApi } from "@/types/serverResponse"; import { useGetAccessToken } from "@/customHooks/useGetAccessToken"; const UserAccess = () => { const updateAccessToken = useStore((state) => state.updateAccessToken); const router = useRouter(); const onSuccess = (resp: AxiosResponse<ServerResponse, any>) => { updateAccessToken(resp.data.data); router.push("/"); }; const onError = (error: unknown) => { console.log("access token api call failed"); console.log(error); }; const { mutate } = useGetAccessToken({ onSuccess, onError, }); useEffect(() => { mutate(); }, []); return ( <motion.div> ...... </motion.div> ); }; export default UserAccess;
现在需要在Server Component中请求仅登录用户可查看的数据,但Server Component无法通过Props传递或直接调用Zustand仓库获取内存中的Access Token,当前Server Component代码如下:
import { cookies } from "next/headers"; import NavbarContainer from "@/components/molecules/home/NavbarContainer"; import { ServerResponse, userApi } from "@/types/serverResponse"; const fetchUser = async (): Promise<ServerResponse | boolean> => { const refreshToken = cookies().get("refresh-token"); if (!refreshToken) { return false; } const resp = await userApi.get<ServerResponse>("/user/me", { withCredentials: true, headers : { Authorization: // 这里需要内存中的access token } }); // 未授权处理 if (resp.status === 401) { } return false; }; const Navbar = async () => { const user = await fetchUser(); console.log(user); return <NavbarContainer />; }; export default Navbar;
试过给Axios设置全局请求头,但只在客户端生效;每次请求都重新获取Access Token会产生大量请求;也不能把Access Token存入Cookie(有安全风险)。请问该怎么调整方案,让Server Component能使用内存中的Access Token进行数据请求?
可行方案1:在Server Component中利用Refresh Token直接获取Access Token
既然Server Component可以读取Cookie中的Refresh Token,那可以直接在服务端发起请求,用Refresh Token换取新的Access Token,再用这个Access Token请求用户数据。可以对这个流程做缓存优化,避免重复请求。
修改后的fetchUser函数示例:
const fetchUser = async (): Promise<ServerResponse | boolean> => { const refreshToken = cookies().get("refresh-token"); if (!refreshToken) { return false; } try { // 第一步:用Refresh Token获取新的Access Token const tokenResp = await userApi.post<ServerResponse>("/token/refresh", { refreshToken: refreshToken.value }, { withCredentials: true }); if (!tokenResp.data.data) { // 刷新失败,返回未登录状态 return false; } const accessToken = tokenResp.data.data; // 第二步:用Access Token请求用户数据 const userResp = await userApi.get<ServerResponse>("/user/me", { headers: { Authorization: `Bearer ${accessToken}` }, withCredentials: true }); if (userResp.status === 200) { return userResp.data; } } catch (error) { console.error("获取用户数据失败:", error); // 处理401等错误,比如清除无效的Refresh Token if (error.response?.status === 401) { // 可选:清除Cookie中的Refresh Token cookies().delete("refresh-token"); } return false; } return false; };
优化建议
- 可以在服务端对获取到的Access Token做短期内存缓存(比如用Redis或者内存缓存库),键用Refresh Token的哈希值,有效期设为Access Token的过期时间,避免每次Server Component渲染都重复请求Token接口。
可行方案2:将需要用户数据的逻辑移到客户端组件
如果Server Component不需要在服务端预渲染用户数据,可以把请求用户数据的部分改成客户端组件,直接从Zustand仓库读取Access Token发起请求,再将数据传递给子组件(包括Server Component)。
示例:
// 客户端组件:UserInfoClient.tsx "use client"; import { useEffect, useState } from "react"; import { useStore } from "@/globalState"; import { ServerResponse, userApi } from "@/types/serverResponse"; import Navbar from "./Navbar"; // 原Server Component const UserInfoClient = () => { const accessToken = useStore((state) => state.accessToken); const [userData, setUserData] = useState<ServerResponse | null>(null); useEffect(() => { const fetchUser = async () => { if (!accessToken) return; try { const resp = await userApi.get<ServerResponse>("/user/me", { headers: { Authorization: `Bearer ${accessToken}` } }); setUserData(resp.data); } catch (error) { console.error("获取用户数据失败:", error); } }; fetchUser(); }, [accessToken]); return <Navbar userData={userData} />; }; export default UserInfoClient;
然后修改原Server Component接收props:
// 原Server Component Navbar.tsx import NavbarContainer from "@/components/molecules/home/NavbarContainer"; import { ServerResponse } from "@/types/serverResponse"; interface NavbarProps { userData: ServerResponse | null; } const Navbar = ({ userData }: NavbarProps) => { console.log(userData); return <NavbarContainer userData={userData} />; }; export default Navbar;
方案3:使用Next.js的Server Actions处理数据请求
可以把请求用户数据的逻辑封装成Server Action,在客户端组件中调用这个Action,Server Action可以读取Cookie中的Refresh Token,自动获取Access Token并请求数据,同时Server Action的结果可以传递给Server Component或者客户端组件使用。
示例:
// app/actions.ts "use server"; import { cookies } from "next/headers"; import { ServerResponse, userApi } from "@/types/serverResponse"; export async function fetchUserAction(): Promise<ServerResponse | boolean> { const refreshToken = cookies().get("refresh-token"); if (!refreshToken) { return false; } try { // 先刷新获取Access Token const tokenResp = await userApi.post<ServerResponse>("/token/refresh", { refreshToken: refreshToken.value }, { withCredentials: true }); if (!tokenResp.data.data) return false; const accessToken = tokenResp.data.data; const userResp = await userApi.get<ServerResponse>("/user/me", { headers: { Authorization: `Bearer ${accessToken}` }, withCredentials: true }); return userResp.data; } catch (error) { console.error("Server Action获取用户数据失败:", error); if (error.response?.status === 401) { cookies().delete("refresh-token"); } return false; } }
然后在客户端组件中调用这个Action:
"use client"; import { useActionState } from "react"; import { fetchUserAction } from "@/app/actions"; import Navbar from "./Navbar"; const UserAccess = () => { const [userData, formAction] = useActionState(fetchUserAction, null); // 组件挂载时触发Action useEffect(() => { formAction(); }, [formAction]); return <Navbar userData={userData} />; }; export default UserAccess;
内容的提问来源于stack exchange,提问作者Diego Davincci

