Next.js登录后导航栏无法自动更新,如何实现无需手动刷新?
问题根源
- 当前Navbar的
useEffect依赖为[user],但登录操作在其他页面完成并设置Cookie后,重定向到首页时,user状态未发生变化,导致useEffect不会触发,无法重新读取Cookie更新登录状态。 - Next.js客户端路由跳转时,layout.tsx中的Navbar组件会复用而非重新挂载,初始
useEffect仅执行一次,登录后不会自动检查状态变化。 - 原代码中
useEffect存在逻辑缺陷:若Cookie存在会反复调用SetUser(true),引发无限循环;同时未处理Cookie不存在时的状态重置。
解决方案
方案1:监听路由变化,实时检查登录状态
通过监听Next.js路由切换事件,每次页面跳转后重新读取Cookie,更新用户状态。
修改Navbar组件的useEffect:
const router = useRouter(); useEffect(() => { const checkUserStatus = () => { const token = Cookies.get("token"); if (token) { const decodedToken = jwtDecode(token); const userData: User = JSON.parse(JSON.stringify(decodedToken)); setExtUser(userData); SetUser(true); } else { // Cookie不存在时重置状态 SetUser(false); setExtUser(undefined); } }; // 初始加载时检查 checkUserStatus(); // 监听路由切换完成事件 const handleRouteChange = () => checkUserStatus(); router.events.on("routeChangeComplete", handleRouteChange); // 组件卸载时清理监听 return () => router.events.off("routeChangeComplete", handleRouteChange); }, [router]);
方案2:使用React Context统一管理登录状态(推荐)
通过全局Context维护登录状态,登录操作直接更新Context,Navbar订阅Context变化实现实时更新,适合中大型项目。
- 创建AuthContext
// app/auth-context.tsx "use client"; import { createContext, useContext, useState, useEffect, ReactNode } from "react"; import Cookies from "js-cookie"; import jwtDecode from "jwt-decode"; import type { User } from "./types"; type AuthContextType = { user: User | undefined; isLoggedIn: boolean; login: (token: string) => void; logout: () => void; }; const AuthContext = createContext<AuthContextType | undefined>(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<User | undefined>(undefined); const [isLoggedIn, setIsLoggedIn] = useState(false); // 初始加载读取Cookie useEffect(() => { const token = Cookies.get("token"); if (token) { const decoded = jwtDecode(token) as User; setUser(decoded); setIsLoggedIn(true); } }, []); // 登录时更新状态并设置Cookie const login = (token: string) => { Cookies.set("token", token); const decoded = jwtDecode(token) as User; setUser(decoded); setIsLoggedIn(true); }; // 登出时清除Cookie并重置状态 const logout = () => { Cookies.remove("token"); setUser(undefined); setIsLoggedIn(false); }; return ( <AuthContext.Provider value={{ user, isLoggedIn, login, logout }}> {children} </AuthContext.Provider> ); } // 自定义Hook获取AuthContext export function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error("useAuth必须在AuthProvider内部使用"); return context; }
- 在layout.tsx中包裹AuthProvider
// app/layout.tsx import { AuthProvider } from "./auth-context"; import Navbar from "./Navbar"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <AuthProvider> <Navbar /> {children} </AuthProvider> </body> </html> ); }
- 修改Navbar组件使用AuthContext
// app/Navbar.tsx "use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "./auth-context"; import Avatar from "./Avatar"; export default function Navbar() { const [menu, setMenu] = useState(false); const router = useRouter(); const { isLoggedIn, user, logout } = useAuth(); const toggleMenu = () => setMenu(!menu); return ( <> <nav className="bg-[#EEEEEE] border-2 ring-slate-900"> {/* 其他导航内容 */} {isLoggedIn ? ( <div className="relative ml-3"> <div> <button type="button" data-dropdown-toggle="dropdownToggle" className={`relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 ${ menu ? "focus:ring-white focus:ring-offset-2" : "focus:ring-offset-gray-800" }`} aria-expanded={menu} aria-haspopup="true" onClick={toggleMenu} > <span className="absolute -inset-1.5"></span> <span className="sr-only">Open user menu</span> <Avatar name={user?.name} className="rounded-full" size="50" /> </button> </div> {menu && ( <div data-dropdown-toggle="dropdownToggle" className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabIndex={-1} > <button onClick={() => router.push("/profile")}> <a className="block px-4 py-2 text-sm text-gray-700 font-semibold" role="menuitem" tabIndex={-1} > Your Profile </a> </button> <button onClick={logout}> <a className="block px-4 py-2 text-sm text-gray-700 font-semibold" role="menuitem" tabIndex={-1} > Sign Out </a> </button> </div> )} </div> ) : ( <button onClick={() => router.push("/login")} className="bg-transparent hover:bg-gray-500 text-black font-bold hover:text-white py-2 px-4 border border-slate-500 hover:border-transparent rounded-lg" > Login </button> )} </nav> </> ); }
- 登录页调用Context的login方法
// app/login/page.tsx "use client"; import { useAuth } from "../auth-context"; import { useRouter } from "next/navigation"; export default function LoginPage() { const { login } = useAuth(); const router = useRouter(); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); // 模拟登录请求 const res = await fetch("/api/login", { method: "POST", body: JSON.stringify({ username: "test", password: "test" }), headers: { "Content-Type": "application/json" }, }); const data = await res.json(); if (data.token) { login(data.token); // 直接更新全局状态 router.push("/"); // 重定向后Navbar自动更新 } }; return ( <form onSubmit={handleLogin} className="p-4"> <input type="text" placeholder="用户名" className="mb-2 p-2 border" /> <input type="password" placeholder="密码" className="mb-2 p-2 border" /> <button type="submit" className="bg-blue-500 text-white p-2 rounded"> 登录 </button> </form> ); }
内容的提问来源于stack exchange,提问作者LOKESH R
相关产品推荐
相关产品推荐

