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
相关产品推荐
相关产品推荐

