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

Next.js登录后导航栏无法自动更新,如何实现无需手动刷新?

解决Next.js登录后Navbar不自动更新的问题

问题根源

  1. 当前Navbar的useEffect依赖为[user],但登录操作在其他页面完成并设置Cookie后,重定向到首页时,user状态未发生变化,导致useEffect不会触发,无法重新读取Cookie更新登录状态。
  2. Next.js客户端路由跳转时,layout.tsx中的Navbar组件会复用而非重新挂载,初始useEffect仅执行一次,登录后不会自动检查状态变化。
  3. 原代码中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变化实现实时更新,适合中大型项目。

  1. 创建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;
}
  1. 在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>
  );
}
  1. 修改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>
    </>
  );
}
  1. 登录页调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 15:45:55