Next.js页面闲置时刷新Cookie值变为null的问题
大伙好,我最近碰到个挺闹心的问题——我把访问令牌和刷新令牌分别存在不同的Cookie里,两个Cookie都设置成7天后过期。访问令牌本身是每10分钟就过期,我还特意做了个刷新令牌的函数,本想着在访问令牌过期前1分钟自动更新状态,活跃用网站的时候一切都正常,但只要闲置一段时间,再回到页面就发现访问令牌的值变成null了,这可把我整懵了。
下面是我当前的AuthContext代码,麻烦大伙帮我看看哪里出问题了:
"use client"; import { createContext, useContext, useState, useEffect } from "react"; import axios from "axios"; import Cookies from "js-cookie"; import jwt from "jsonwebtoken"; import { useRouter } from "next/navigation"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const apiURL = process.env.NEXT_PUBLIC_API_URL; const router = useRouter(); const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const storedToken = Cookies.get("token"); if (storedToken) { setToken(storedToken); } else { logoutAction(); } }, []); useEffect(() => { const storedUser = JSON.parse(localStorage.getItem("user")); if (storedUser) { if (storedUser.roleTypeId === 3) { router.push("/dashboard"); } } }, [user]); useEffect(() => { const initializeAuth = async () => { setLoading(true); console.log(token); try { const storedUser = localStorage.getItem("user"); if (storedUser) setUser(JSON.parse(storedUser)); const currentToken = Cookies.get("token"); console.log(currentToken); if (currentToken) { const decodedToken = jwt.decode(currentToken); if (Date.now() >= decodedToken?.exp * 1000) { await handleTokenRefresh(); } } else { logoutAction(); } } catch (error) { console.error("Error during auth initialization:", error); } finally { setLoading(false); } }; initializeAuth(); }, [token]); useEffect(() => { const intervalId = setInterval(async () => { try { if (token) { const decodedToken = jwt.decode(token); if (Date.now() >= decodedToken?.exp * 1000) { await handleTokenRefresh(); } } } catch (error) { throw error; } }, 60000); return () => { clearInterval(intervalId); }; }); const handleTokenRefresh = async () => { try { const currentRefreshToken = Cookies.get("refreshToken"); const currentToken = Cookies.get("token"); const { data } = await axios.post( `${apiURL}/Authentication/RefreshToken`, { refreshToken: currentRefreshToken, token: currentToken, }, { headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `bearer ${token}`, }, } ); updateTokens(data.token, data.refreshToken); return data; } catch (error) { console.error("Error refreshing token:", error); logoutAction(); throw error; } }; const updateTokens = ( newToken , newRefreshToken) => { const cookieOptions = { secure: true, sameSite: "strict", expires: 7, path: "/", domain: window.location.hostname, }; Cookies.set("token", newToken , cookieOptions); Cookies.set("refreshToken", newRefreshToken , cookieOptions); setToken( newToken ); }; const LoginAction = async ( loginData ) => { setLoading(true); try { const { data } = await axios.post( `${apiURL}/Authentication/LoginUser`, loginData , { headers: { "Content-Type": "application/json", Accept: "application/json", }, } ); updateTokens( data.authenticationResult.token, data.authenticationResult.refreshToken ); localStorage.setItem("user", JSON.stringify(data.user)); setUser(data.user); return data; } catch (error) { throw error.response; } finally { setLoading(false); } }; const logoutAction = () => { Cookies.remove("token"); Cookies.remove("refreshToken"); localStorage.removeItem("user"); setUser(null); setToken(null); router.push("/"); }; return ( <AuthContext.Provider value ={{ LoginAction, logoutAction, user, loading }}> { children } </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
我琢磨出来的问题点和修复建议
自己捣鼓了一阵,大概找到几个可能导致这个问题的地方,给你针对性的修复思路:
- 固定轮询定时器不精准,后台页面被节流
当前你用每分钟固定轮询的方式检查Token,但浏览器对后台标签的定时器会做节流,闲置时可能没及时触发刷新。改成根据Token过期时间计算延迟,用setTimeout代替setInterval更靠谱:
useEffect(() => { let refreshTimer; if (token) { const decodedToken = jwt.decode(token); if (!decodedToken) return; // 提前1分钟触发刷新 const timeUntilExpire = decodedToken.exp * 1000 - Date.now(); const refreshDelay = Math.max(timeUntilExpire - 60 * 1000, 0); refreshTimer = setTimeout(async () => { await handleTokenRefresh(); }, refreshDelay); } return () => clearTimeout(refreshTimer); }, [token]);
- Cookie的domain设置导致无法持久化
你设置了domain: window.location.hostname,在localhost本地开发或者跨子域名场景下,这个设置会让Cookie无法正确保存,闲置后被浏览器清掉。改成只在生产环境配置明确的域名:
const cookieOptions = { secure: process.env.NODE_ENV === "production", // 生产环境再开secure sameSite: "strict", expires: 7, path: "/", ...(process.env.NODE_ENV === "production" && process.env.NEXT_PUBLIC_COOKIE_DOMAIN ? { domain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN } : {}) };
- 刷新Token时带了过期的Authorization头
刷新Token的接口应该只需要请求体里的refreshToken和旧Token,不需要带Authorization头(毕竟旧Token可能已经过期了),去掉这个头避免请求失败:
const handleTokenRefresh = async () => { try { const currentRefreshToken = Cookies.get("refreshToken"); const currentToken = Cookies.get("token"); const { data } = await axios.post( `${apiURL}/Authentication/RefreshToken`, { refreshToken: currentRefreshToken, token: currentToken }, { headers: { "Content-Type": "application/json", Accept: "application/json" } } ); updateTokens(data.token, data.refreshToken); return data; } catch (error) { console.error("刷新Token失败:", error); logoutAction(); throw error; } };
- 多useEffect初始化逻辑有竞态
你有三个处理Auth初始化的useEffect,可能导致状态更新冲突。合并成一个初始化函数,确保逻辑顺序正确:
useEffect(() => { const initializeAuth = async () => { setLoading(true); try { const storedToken = Cookies.get("token"); const storedUser = localStorage.getItem("user"); if (storedToken) { setToken(storedToken); const decodedToken = jwt.decode(storedToken); if (Date.now() >= decodedToken?.exp * 1000) { await handleTokenRefresh(); } } else { logoutAction(); } if (storedUser) { const parsedUser = JSON.parse(storedUser); setUser(parsedUser); if (parsedUser.roleTypeId === 3) router.push("/dashboard"); } } catch (error) { console.error("初始化Auth出错:", error); logoutAction(); } finally { setLoading(false); } }; initializeAuth(); }, []);
- 监听页面从后台激活的事件
加上页面可见性变化的监听,当用户从后台切回页面时,立即检查Token状态,必要时刷新:
useEffect(() => { const handlePageVisible = async () => { if (document.visibilityState === "visible" && token) { const decodedToken = jwt.decode(token); if (Date.now() >= decodedToken?.exp * 1000) { await handleTokenRefresh(); } } }; document.addEventListener("visibilitychange", handlePageVisible); return () => document.removeEventListener("visibilitychange", handlePageVisible); }, [token]);
按照这些建议改完后,应该就能解决闲置时Token变成null的问题了,你可以一步步试试~
备注:内容来源于stack exchange,提问作者Mostafa Ehab
相关产品推荐
相关产品推荐

