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

Next.js 14登录/登出流程异常:跳转后头部显示错误

Next.js 14 登录/登出后头部状态不即时更新问题排查与解决

问题描述

我是Server Components新手,在Next.js 14中实现登录/登出功能,将access_token存储在cookie中。已编写登录路由处理、动态头部、checkToken服务端动作、登出服务端动作、首页及根布局代码,但登录后跳转首页、点击登出时头部显示错误,刷新浏览器后才恢复正常,不清楚问题出在哪里。

现有代码

登录路由处理 /api/login/route.ts

import { api } from '@/lib/axios';
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { LoginResponseDto } from '@/types/auth';
import { revalidatePath } from 'next/cache';

export async function POST(request: NextRequest) {
  try {
    const data = await request.json();

    const res = await api.post<LoginResponseDto>(
      `${process.env.API_BASE_URL}/login_check`,
      data
    );

    if (res.status !== 200) {
      return NextResponse.json(
        {
          error: 'Failed to login',
        },
        { status: res.status }
      );
    }

    const token = res.data.token;

    const response = NextResponse.json(
      { success: true, message: 'Login successful' },
      { status: res.status }
    );

    response.cookies.set('access_token', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 60 * 60 * 24 * 7,
      path: '/',
    });
    revalidatePath('/');

    return response;
  } catch (error) {
    return NextResponse.json(
      {
        error: error instanceof Error ? error.message : 'Failed to login',
      },
      { status: 500 }
    );
  }
}

动态头部组件

import { checkToken } from '@/app/actions/checkToken/checkToken';
import Header from './Header';
import HeaderDashboard from './HeaderDashboard';

export default async function DynamicHeader() {
  const token = await checkToken();

  return token?.value ? <HeaderDashboard /> : <Header />;
}

checkToken服务端动作

'use server'
import { cookies } from 'next/headers';

export async function checkToken() {
  const cookieStore = cookies();
  const token = cookieStore.get('access_token');
  return token;
}

登出服务端动作

'use server';
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
import { revalidatePath } from 'next/cache';

export async function logout() {
  const cookieStore = cookies();
  const token = cookieStore.get('access_token');
  if (token) {
    cookieStore.delete('access_token');
    revalidatePath('/', 'page');
  }

  redirect('/');
}

首页代码

// import HomeOne from "@/components/homes/home";
import Header from '../components/layout/headers/Header';
// import MobileMenu from "@/components/layout/component/MobileMenu";

import HomeHero from '../components/homes/heros/HomeHero';

import Courses from '../components/homes/courses/Courses';
import FooterOne from '../components/layout/footers/FooterOne';
import Preloader from '@/components/common/Preloader';
import { api } from '@/lib/axios';
import { GenericResponse } from '@/types/response';
import { ICourse } from '@/types/course';

export const dynamic = 'force-dynamic';
// export const fetchCache = 'no-store';
// export const revalidate = 0;

export const metadata = {
  title: 'Home | **',
  description: '***',
};

export default async function HomePage() {
  const res = await api.get<GenericResponse<ICourse[]>>('/public/home/course');
  return (
    <>
      <Preloader />
      <Header />
      <div className="content-wrapper  js-content-wrapper overflow-hidden">
        <HomeHero />
        <Courses courses={res.data.data} />
        <FooterOne />
      </div>
    </>
  );
}

根布局代码

'use client';
// import "bootstrap/dist/css/bootstrap.min.css";
import '../public/assets/sass/styles.scss';
import { config } from '@fortawesome/fontawesome-svg-core';
import '@fortawesome/fontawesome-svg-core/styles.css';
import 'react-calendar/dist/Calendar.css';
import 'react-toastify/dist/ReactToastify.css';
config.autoAddCss = false;

import AOS from 'aos';
import 'aos/dist/aos.css';
import { useEffect } from 'react';
import Context from '@/context/Context';
import { ToastContainer } from 'react-toastify';

export default function RootLayout({ children }) {
  useEffect(() => {
    AOS.init({
      duration: 700,
      offset: 120,
      easing: 'ease-out',
      once: true,
    });
  }, []);
  return (
    <html lang="en" className="">
      <head></head>
      <body>
        <ToastContainer />
        <Context> {children}</Context>
      </body>
    </html>
  );
}

问题原因分析

核心问题是客户端路由跳转时,Server Components的缓存未即时更新:

  • 登录后:前端调用登录接口成功后,仅在服务端设置cookie并调用revalidatePath('/'),但如果用客户端路由(如useRouter.push)跳转首页,首页的Server Components(含动态头部)仍使用缓存数据,未重新执行checkToken获取最新cookie状态。
  • 登出后:服务端动作删除cookie并执行revalidatePath,但redirect('/')的服务端跳转可能和客户端状态不同步,客户端路由缓存未失效。

解决方案

1. 登录后客户端强制刷新路由

若登录逻辑在客户端组件中,登录成功后调用router.refresh()强制刷新页面,确保Server Components重新获取最新状态:

'use client';
import { useRouter } from 'next/navigation';

async function handleLogin(username: string, password: string) {
  const res = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify({ username, password }),
    headers: { 'Content-Type': 'application/json' }
  });
  
  if (res.ok) {
    const router = useRouter();
    router.refresh(); // 强制刷新,触发Server Components重新渲染
    router.push('/');
  }
}

2. 首页替换为动态头部组件

检查首页代码,将普通Header替换为DynamicHeader,确保头部能根据cookie状态动态切换:

// 首页代码修改
import DynamicHeader from '../components/layout/headers/DynamicHeader'; // 替换原Header导入

export default async function HomePage() {
  const res = await api.get<GenericResponse<ICourse[]>>('/public/home/course');
  return (
    <>
      <Preloader />
      <DynamicHeader /> {/* 使用动态头部 */}
      <div className="content-wrapper  js-content-wrapper overflow-hidden">
        <HomeHero />
        <Courses courses={res.data.data} />
        <FooterOne />
      </div>
    </>
  );
}

3. 登出后客户端同步刷新

若登出按钮是客户端组件,调用服务端动作后执行router.refresh(),确保客户端状态与服务端同步:

'use client';
import { logout } from '@/app/actions/logout';
import { useRouter } from 'next/navigation';

async function handleLogout() {
  await logout();
  const router = useRouter();
  router.refresh();
}

4. 保留首页的动态渲染配置

首页已设置dynamic: 'force-dynamic',确保页面不会被静态缓存,该配置正确,继续保留即可。

总结

通过上述调整,登录/登出操作后头部状态会即时更新,无需手动刷新浏览器。核心是确保客户端路由跳转时,强制刷新Server Components缓存,让checkToken重新执行以获取最新的cookie状态。

内容的提问来源于stack exchange,提问作者Arsany Benyamin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:08:10