Next.js未登录用户跳转登录页问题及报错求助
问题:Next.js自建认证系统无法实现未登录重定向,触发SecurityError
我用Next.js开发应用(看中它的文件路由机制),自己搭建认证系统(不用第三方库),用户能正常注册登录,登录后从Express服务器获取JWT并存入localStorage,但始终无法实现未认证用户访问页面自动跳转到/login的功能。
在useEffect中调用router.push("/login")时,出现以下错误:
Unhandled Runtime Error
SecurityError: The operation is insecure.
控制台同时输出两个提示:
- Too many calls to Location or History APIs within a short timeframe.
- Uncaught (in promise) DOMException: The operation is insecure.
相关代码
UserSession.tsx
import { createContext, useEffect, useState, useCallback } from 'react'; import { useRouter } from 'next/router'; interface LoginData { "token": string, "user_id": string, "username": string, "first_name": string, "last_name": string, "profile_picture": string } interface IUserSessionContext { isLoggedIn: boolean; username: string; login: (data: LoginData) => void; logout: () => void; } const UserSessionContext = createContext<IUserSessionContext>({ isLoggedIn: false, username: "", login: () => {}, logout: () => {} }); const UserSessionProvider = () => { const [username, setUsername] = useState<string>(""); const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); const router = useRouter(); useEffect(() => { // Check if user has token const token = localStorage.getItem('token'); const userData = localStorage.getItem('userData'); if (token && userData) { const parsedData: LoginData = JSON.parse(userData); setUsername(parsedData.username); setIsLoggedIn(true) } }, []); const login = useCallback((data: LoginData) => { const userData: LoginData = data; setUsername(userData.username); setIsLoggedIn(true) localStorage.setItem('token', userData.token); localStorage.setItem('userData', JSON.stringify(userData)); router.push("/"); }, [router]); const logout = useCallback(() => { const name = ""; const loggedIn = false; localStorage.removeItem('token'); localStorage.removeItem('userData'); setUsername(name); setIsLoggedIn(loggedIn); router.push("/login"); }, [router]); return { username, isLoggedIn, login, logout} }; export { UserSessionProvider, UserSessionContext };
_app.tsx
import { UserSessionContext, UserSessionProvider } from '@/components/context/UserSession' import PageHeader from '@/components/PageHeader' import UserSideBar from '@/components/UserSideBar' import '@/styles/globals.css' import { useRouter } from 'next/router' import Script from 'next/script' import { useContext, useEffect } from 'react' type AppProps = { Component: React.ComponentType<any>, pageProps: any } export default function App({ Component, pageProps }: AppProps) { const router = useRouter(); const userSessionData = useContext(UserSessionContext); const pageToRender = () => { if (router?.pathname === '/login' || router?.pathname === '/register') { return <Component {...pageProps} /> } else { return ( <> <UserSideBar /> <div className='pageContent'> <Component {...pageProps} /> </div> </> ) } } const { username, isLoggedIn, login, logout } = UserSessionProvider(); useEffect(() => { if(!userSessionData.isLoggedIn){ router.push("/login") } }) return ( <UserSessionContext.Provider value={{ username, isLoggedIn, login, logout }}> <PageHeader /> {pageToRender()} </UserSessionContext.Provider> ) }
问题原因及修复方案
核心问题
- Context使用顺序错误:
_app.tsx中先调用useContext获取值,但此时Provider还未渲染,拿到的是默认的isLoggedIn: false,导致每次渲染都触发重定向。 - useEffect无限循环:未给
useEffect设置依赖项,会在组件每次渲染时执行,加上重定向后再次触发渲染,形成无限循环,浏览器限制History API调用后抛出SecurityError。 - 未排除公共页面:重定向逻辑没有判断当前是否在
/login或/register,导致在登录页也会重复跳转。
修复步骤
1. 修正UserSessionProvider组件结构
让UserSessionProvider返回Context.Provider包裹子组件,而不是普通对象:
// 修改UserSession.tsx中的UserSessionProvider const UserSessionProvider = ({ children }: { children: React.ReactNode }) => { const [username, setUsername] = useState<string>(""); const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false); const router = useRouter(); useEffect(() => { const token = localStorage.getItem('token'); const userData = localStorage.getItem('userData'); if (token && userData) { const parsedData: LoginData = JSON.parse(userData); setUsername(parsedData.username); setIsLoggedIn(true) } }, []); const login = useCallback((data: LoginData) => { const userData: LoginData = data; setUsername(userData.username); setIsLoggedIn(true); localStorage.setItem('token', userData.token); localStorage.setItem('userData', JSON.stringify(userData)); router.push("/"); }, [router]); const logout = useCallback(() => { localStorage.removeItem('token'); localStorage.removeItem('userData'); setUsername(""); setIsLoggedIn(false); router.push("/login"); }, [router]); return ( <UserSessionContext.Provider value={{ username, isLoggedIn, login, logout }}> {children} </UserSessionContext.Provider> ); };
2. 重构_app.tsx逻辑
将Provider放在最外层,在Provider内部使用Context,并给useEffect添加正确依赖项,排除公共页面:
import { UserSessionProvider } from '@/components/context/UserSession' import PageHeader from '@/components/PageHeader' import UserSideBar from '@/components/UserSideBar' import '@/styles/globals.css' import { useRouter } from 'next/router' import { useContext, useEffect } from 'react' import { UserSessionContext } from '@/components/context/UserSession' type AppProps = { Component: React.ComponentType<any>, pageProps: any } export default function App({ Component, pageProps }: AppProps) { return ( <UserSessionProvider> <AppContent Component={Component} pageProps={pageProps} /> </UserSessionProvider> ) } function AppContent({ Component, pageProps }: AppProps) { const router = useRouter(); const { isLoggedIn } = useContext(UserSessionContext); const pageToRender = () => { if (router.pathname === '/login' || router.pathname === '/register') { return <Component {...pageProps} /> } else { return ( <> <UserSideBar /> <div className='pageContent'> <Component {...pageProps} /> </div> </> ) } } useEffect(() => { const publicPaths = ['/login', '/register']; if (!isLoggedIn && !publicPaths.includes(router.pathname)) { router.push("/login"); } }, [isLoggedIn, router.pathname]); return ( <> <PageHeader /> {pageToRender()} </> ) }
额外优化建议
- 监听
router.events在路由变化时做认证检查,避免组件渲染时的内容闪现。 - 敏感页面结合
getServerSideProps做服务端认证校验,提升安全性。
内容的提问来源于stack exchange,提问作者mitas1c
相关产品推荐
相关产品推荐

