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

Next.js未登录用户跳转登录页问题及报错求助

问题:Next.js自建认证系统无法实现未登录重定向,触发SecurityError

我用Next.js开发应用(看中它的文件路由机制),自己搭建认证系统(不用第三方库),用户能正常注册登录,登录后从Express服务器获取JWT并存入localStorage,但始终无法实现未认证用户访问页面自动跳转到/login的功能。

在useEffect中调用router.push("/login")时,出现以下错误:

Unhandled Runtime Error

SecurityError: The operation is insecure.

控制台同时输出两个提示:

  1. Too many calls to Location or History APIs within a short timeframe.
  2. 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>
  )
}

问题原因及修复方案

核心问题

  1. Context使用顺序错误:_app.tsx中先调用useContext获取值,但此时Provider还未渲染,拿到的是默认的isLoggedIn: false,导致每次渲染都触发重定向。
  2. useEffect无限循环:未给useEffect设置依赖项,会在组件每次渲染时执行,加上重定向后再次触发渲染,形成无限循环,浏览器限制History API调用后抛出SecurityError。
  3. 未排除公共页面:重定向逻辑没有判断当前是否在/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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:44:59