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

Next.js中间件引发导航延迟,寻求优化解决方案

Next.js 中间件验证导致页面跳转卡顿的优化方案

核心优化思路:优先完成路由跳转,再异步处理验证逻辑

把原本在中间件中阻塞跳转的身份验证、令牌刷新逻辑,拆分到「客户端跳转触发」和「目标页面异步校验」两个阶段,让用户先看到新路由和加载状态,而非卡在原页面等待。


方案1:客户端路由跳转优先,目标页面异步验证(Pages Router)

1.1 修改跳转逻辑,跳过中间件前置阻塞

直接使用 useRouter 发起跳转,不等待验证完成:

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

export default function NavButton() {
  const router = useRouter();

  const handleJump = async () => {
    // 先执行跳转,用户立刻看到新路由
    await router.push('/protected-page');
    // 跳转后异步处理令牌刷新(可选,也可放到目标页面做)
    const refreshToken = localStorage.getItem('refreshToken');
    if (需要刷新令牌的判断逻辑) {
      await fetch('/api/refresh-token', {
        method: 'POST',
        body: JSON.stringify({ refreshToken })
      });
    }
  };

  return <button onClick={handleJump}>进入受保护页面</button>;
}

1.2 目标页面添加加载状态+异步验证

在目标页面中处理身份校验,验证完成前显示加载UI:

'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

export default function ProtectedPage() {
  const [isLoading, setIsLoading] = useState(true);
  const router = useRouter();

  useEffect(() => {
    const validateAuth = async () => {
      try {
        const token = localStorage.getItem('token');
        // 校验令牌有效性
        const isValid = await fetch('/api/check-token', {
          method: 'POST',
          body: JSON.stringify({ token })
        }).then(res => res.json());

        if (!isValid) {
          // 尝试刷新令牌
          const refreshToken = localStorage.getItem('refreshToken');
          const refreshRes = await fetch('/api/refresh-token', {
            method: 'POST',
            body: JSON.stringify({ refreshToken })
          });

          if (!refreshRes.ok) {
            // 刷新失败,跳转到登录页
            router.push('/login');
            return;
          }
          const newToken = await refreshRes.json();
          localStorage.setItem('token', newToken.accessToken);
        }
      } catch (err) {
        router.push('/login');
      } finally {
        setIsLoading(false);
      }
    };

    validateAuth();
  }, [router]);

  if (isLoading) {
    return <div className="loading">页面加载中...</div>; // 自定义加载UI
  }

  return <div>受保护页面内容</div>;
}

方案2:利用App Router内置能力(推荐,适配新版Next.js)

如果使用App Router,直接借助 loading.tsx 实现跳转后加载状态,同时在路由布局中做异步验证:

2.1 路由目录下添加加载组件

在受保护路由的目录中创建 loading.tsx,跳转时自动显示:

// app/protected/loading.tsx
export default function Loading() {
  return <div className="loading">正在加载页面...</div>;
}

2.2 在布局组件中做异步验证

把验证逻辑移到路由的服务器端布局中,验证失败直接重定向:

// app/protected/layout.tsx
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';

export default async function ProtectedLayout({ children }) {
  const token = cookies().get('token')?.value;
  const refreshToken = cookies().get('refreshToken')?.value;

  // 校验令牌有效性
  const isValid = await fetch('http://localhost:3000/api/check-token', {
    method: 'POST',
    body: JSON.stringify({ token }),
    cache: 'no-store' // 不缓存,确保每次校验最新状态
  }).then(res => res.json());

  if (!isValid) {
    if (!refreshToken) redirect('/login');
    
    // 尝试刷新令牌
    const refreshRes = await fetch('http://localhost:3000/api/refresh-token', {
      method: 'POST',
      body: JSON.stringify({ refreshToken })
    });

    if (!refreshRes.ok) redirect('/login');
    
    // 更新Cookie(服务器端设置)
    const newToken = await refreshRes.json();
    const response = new Response(null, { status: 200 });
    response.cookies.set('token', newToken.accessToken, { httpOnly: true });
    return response;
  }

  return <div>{children}</div>;
}

2.3 客户端跳转逻辑

使用 useNavigation 发起跳转,无需等待:

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

export default function NavButton() {
  const navigation = useNavigation();

  const handleJump = () => {
    navigation.push('/protected/target');
  };

  return <button onClick={handleJump}>进入受保护页面</button>;
}

辅助优化:精简中间件逻辑

如果必须保留中间件,可通过缓存验证结果、缩小作用范围减少阻塞:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  // 只对受保护路由生效,缩小中间件作用范围
  if (!request.nextUrl.pathname.startsWith('/protected')) {
    return NextResponse.next();
  }

  const token = request.cookies.get('token')?.value;
  const refreshToken = request.cookies.get('refreshToken')?.value;

  // 缓存令牌有效性校验结果,避免重复请求
  const isValid = await fetch(new URL('/api/check-token', request.url), {
    method: 'POST',
    body: JSON.stringify({ token }),
    cache: 'force-cache',
    next: { revalidate: 60 } // 1分钟内复用校验结果
  }).then(res => res.json());

  if (isValid) return NextResponse.next();

  // 仅当令牌无效时才触发刷新逻辑
  if (refreshToken) {
    try {
      const refreshRes = await fetch(new URL('/api/refresh-token', request.url), {
        method: 'POST',
        body: JSON.stringify({ refreshToken })
      });
      if (refreshRes.ok) {
        const newToken = await refreshRes.json();
        const response = NextResponse.next();
        response.cookies.set('token', newToken.accessToken, { httpOnly: true });
        return response;
      }
    } catch (err) {}
  }

  return NextResponse.redirect(new URL('/login', request.url));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:54:50