解决NextJS路由重定向前短暂加载未授权页面的问题
解决Next.js中/app/路由未授权页面短暂加载的问题
你的问题根源在于授权校验是在客户端组件挂载后(useEffect中)才执行,这就导致未授权页面会先渲染出来,之后才触发重定向。要解决这个问题,需要把授权校验提前到路由加载之前完成,Next.js提供了两种更合适的方案:
方案一:使用Middleware(推荐,Next.js 12+)
Middleware会在所有路由请求到达页面之前运行,不管是SSG、SSR还是CSR页面都能生效,是最彻底的解决方式。
- 在项目根目录创建
middleware.ts文件:
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // 匹配所有/app/开头的路由 const isAppRoute = request.nextUrl.pathname.startsWith('/app/') // 排除登录页本身 const isLoginRoute = request.nextUrl.pathname === '/login/' if (isAppRoute && !isLoginRoute) { // 从请求Cookie中获取会话令牌(适配后端Cookie存储会话的逻辑) const sessionToken = request.cookies.get('session_token')?.value if (!sessionToken) { // 无会话令牌,直接重定向到登录页,可保留原请求地址作为回调 return NextResponse.redirect(new URL('/login/?redirect=' + request.nextUrl.pathname, request.url)) } // 可选:发起请求到后端校验会话有效性 // const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/session/`, { // headers: { Cookie: `session_token=${sessionToken}` } // }) // const data = await res.json() // if (!data.authenticated) { // return NextResponse.redirect(new URL('/login/', request.url)) // } } // 校验通过,继续路由 return NextResponse.next() } // 指定Middleware生效的路由范围 export const config = { matcher: ['/app/:path*', '/login/'] }
- 注意事项:
- Middleware运行在Edge Runtime环境,不能使用Node.js特定API,要用
fetch代替axios - 优先从Cookie读取会话,避免重复发起请求,提升性能
- 可保留原请求地址作为
redirect参数,登录成功后跳转回原页面
- Middleware运行在Edge Runtime环境,不能使用Node.js特定API,要用
方案二:使用getServerSideProps(针对Pages Router)
如果你的项目用Pages Router,可以在每个需要授权的/app/页面中添加getServerSideProps,在服务器端提前校验:
比如在/app/profile.js中:
import axios from 'axios' import { getUserState } from '../context/AppSession' export async function getServerSideProps(context) { const { req, res } = context try { // 携带请求Cookie发起会话校验 const sessionRes = await axios.get('/api/auth/session/', { headers: { Cookie: req.headers.cookie } }) const { authenticated } = sessionRes.data if (!authenticated) { // 未授权,直接重定向 res.writeHead(302, { Location: '/login/' }) res.end() return { props: {} } } // 校验通过,将用户数据传递给页面组件 return { props: { user: sessionRes.data } } } catch (err) { // 请求失败,默认重定向到登录页 res.writeHead(302, { Location: '/login/' }) res.end() return { props: {} } } } export default function Profile() { const { user } = getUserState() return <div>用户Profile:{user.user_email}</div> }
修正原客户端SessionContext代码(辅助优化)
如果还需要保留客户端的会话状态管理,可以优化原代码的依赖数组和初始化逻辑,避免不必要的请求:
import React, { createContext, useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' import axios from 'axios' export const SessionContext = createContext(null); const AppSession = ({ children, initialUser }) => { const router = useRouter() // 初始化时明确authenticated状态为undefined const [user, setUser] = useState(initialUser || { user_id: '', user_email: '', user_avatar: '', authenticated: undefined}) useEffect(()=> { const isAppRoute = router.pathname.includes("/app/") // 仅在/app/路由且未完成校验时发起请求 if (isAppRoute && user.authenticated === undefined){ axios.get('/api/auth/session/') .then(res => { const data = res.data; setUser(data) if (!data.authenticated){ router.push('/login/') } }) .catch(err => { console.log(err) router.push('/login/') }); } }, [router.pathname, user.authenticated]) // 添加路由路径和authenticated作为依赖 return ( <SessionContext.Provider value={{user, setUser}}> {children} </SessionContext.Provider> ) } export const getUserState = () => { const { user } = useContext(SessionContext) return user; } export const updateUserState = () => { const { setUser } = useContext(SessionContext) return (user) => setUser(user); } export default AppSession;
内容的提问来源于stack exchange,提问作者cphill
相关产品推荐
相关产品推荐

