Next.js 13如何使用LocalStorage实现路由守卫?
问题解决与方案汇总
核心问题原因
你当前的代码失效是因为Next.js Middleware运行在服务端(Edge Runtime),而localStorage是浏览器专属API,服务端无法读取浏览器的LocalStorage数据。所以不管用户是否登录,getUserFromLocalStorage()都会返回null,导致始终重定向到登录页。
立即修复方案:改用Cookie存储认证信息
服务端Middleware可以读取请求头里的Cookie,这是服务端校验登录状态的标准方式:
- 修改登录逻辑:登录成功后,将用户信息(或简化的token)存入Cookie,而非LocalStorage:
// 登录成功后执行 document.cookie = `user=${JSON.stringify(userData)}; path=/; max-age=86400; SameSite=Lax`; // 可选:同时存入LocalStorage做客户端缓存 localStorage.setItem('user', JSON.stringify(userData));
- 修正Middleware代码:从请求Cookie中读取用户信息:
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export async function middleware(request: NextRequest) { // 从Cookie中读取用户信息 const userCookie = request.cookies.get('user')?.value; const user = userCookie ? JSON.parse(userCookie) : null; // 排除登录页,避免死循环 if (!user && request.nextUrl.pathname !== '/login') { return NextResponse.redirect(new URL('/login', request.url)) } // 已登录用户访问登录页时,重定向到主页 if (user && request.nextUrl.pathname === '/login') { return NextResponse.redirect(new URL('/', request.url)) } } export const config = { // 匹配所有需要授权的路由,可根据需求调整 matcher: ['/', '/dashboard/:path*', '/profile/:path*'] }
LocalStorage/IndexedDB实现路由守卫的常用方案
这类存储属于客户端缓存,适合做客户端路由守卫,搭配服务端校验使用:
方案1:全局客户端守卫(在根组件中实现)
在Next.js的根组件中,监听路由变化并校验登录状态:
// pages/_app.tsx 或 app/layout.tsx(App Router) import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { getUserFromLocalStorage } from '../lib/utils'; export default function MyApp({ Component, pageProps }) { const router = useRouter(); useEffect(() => { const checkAuth = () => { const user = getUserFromLocalStorage(); const isLoginPage = router.pathname === '/login'; if (!user && !isLoginPage) { router.push('/login'); } if (user && isLoginPage) { router.push('/'); } }; // 初始加载和路由变化时校验 checkAuth(); const unsubscribe = router.events.on('routeChangeComplete', checkAuth); return () => unsubscribe(); }, [router]); return <Component {...pageProps} />; }
注意:这种方式会有短暂的未授权页面闪屏,因为是客户端渲染完成后才执行跳转。
方案2:页面级客户端守卫(单个页面)
对需要授权的页面单独添加校验:
// pages/dashboard.tsx import { useEffect } from 'react'; import { useRouter } from 'next/router'; import { getUserFromLocalStorage } from '../lib/utils'; export default function Dashboard() { const router = useRouter(); useEffect(() => { const user = getUserFromLocalStorage(); if (!user) { router.push('/login'); } }, [router]); return <div>仪表盘内容</div>; }
方案3:IndexedDB进阶缓存(适合大量用户数据)
如果需要缓存更多用户数据,可使用IndexedDB,搭配localForage等库简化操作,逻辑和LocalStorage一致:
// utils/indexedDB.ts import localForage from 'localforage'; export const getUserFromIndexedDB = async (): Promise<User | null> => { try { const user = await localForage.getItem('user'); return user as User | null; } catch (err) { console.error('读取IndexedDB失败:', err); return null; } };
然后在客户端守卫中调用这个异步函数做校验。
最佳实践建议
- 服务端Middleware负责强制路由拦截(用Cookie),避免未授权用户直接访问页面内容
- 客户端LocalStorage/IndexedDB负责缓存用户信息,减少服务端请求,提升体验
- 敏感操作(如API请求)仍需在服务端校验用户token的有效性,不能仅依赖客户端缓存
内容的提问来源于stack exchange,提问作者Eduardo Fellipe
相关产品推荐
相关产品推荐

