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

React Router私有路由异常:未验证用户跳转/verify后空白页及登录问题

React Router私有路由与登录跳转问题排查与解决

需求说明

  • 未登录用户访问私有路由时自动跳转至 /login
  • 已登录但未完成验证的用户跳转至 /verify
  • 已验证用户正常进入目标页面

问题现象

  1. 未验证用户被跳转至 /verify 后,URL显示正确,但页面空白,Verify 组件未渲染
  2. 登录逻辑中必须添加 setTimeout 才能根据用户验证状态正确跳转,否则无论是否验证都会直接跳转到 /socialcalendar

相关代码

PrivateRoute 实现

import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';

const PrivateRoute = () => {
    const isLoggedIn = useSelector((state: any) => state.auth.authentication);
    const profile: any = isLoggedIn?.profile;
    const isVerified: boolean = profile?.verified;
    const hasSubscription: any = profile?.subscription;
    console.log('verified: ', isVerified);
    if (isLoggedIn) {
        if (!isVerified) {
            console.log('need to go to verify');
                return <Navigate to='/verify' replace />;
        } else {
        return <Outlet />;
        }
    } else {
        return <Navigate to='/login' />;
    }

    return <Navigate to='/login' />;

    // return isLoggedIn ? <Outlet /> : <Navigate to='/login' />;
};

export default PrivateRoute;

路由配置(app.tsx)

<BrowserRouter>
  <Routes>
    <Route path='/login' element={<PublicRoute />}>
      <Route path='/login' element={<Login />} />
    </Route>
    <Route path='/register' element={<PublicRoute />}>
      <Route path='/register' element={<Register />} />
    </Route>
    <Route path='/reset-password' element={<PublicRoute />}>
      <Route path='/reset-password' element={<ResetPassword />} />
    </Route>
    <Route path='/forgot-password' element={<PublicRoute />}>
      <Route path='/forgot-password' element={<ForgetPassword />} />
    </Route>
    {/* <Route path='/subscription' element={<PublicRoute />}>
      <Route path='/subscription' element={<Subscription />} />
    </Route> */}

    <Route path='/' element={<PrivateRoute />}>
      <Route path='/verify' element={<Verify />} />
      <Route path='/' element={<Main />}>
        <Route path='/profile' element={<Profile />} />
        <Route path='/socialcalendar' element={<SocialCalendar />} />
        <Route path='/subscription' element={<Subscription />} />
        <Route index element={<Navigate to='/socialcalendar' />} />
        {/* <Route path='/' element={<Daily />} />
        <Route path='/dashboard' element={<Dashboard />} />
      </Route>
    </Route>
    <Route path='*' element={<Navigate to='/login' />} />
  </Routes>
</BrowserRouter>

登录逻辑代码

const login = async (email: string, password: string) => {
        try {
            console.log('login requesrt sent from login');

            setAuthLoading(true);

            const response: any = await authLogin(email, password);
            dispatch(setAuthentication(response as any));
            toast.success('Login is succeeded!');
            setAuthLoading(false);
            console.log(response.profile.verified);
            await new Promise((resolve) => setTimeout(resolve, 100));
            // if (!response.profile.verified) {
            //  navigate('/verify');
            // } else {
            //  navigate('/socialcalendar')
            //  // if (
            //  //  !response.profile?.subscription ||
            //  //  response.profile.subscription.status === 'paused' ||
            //  //  (response.profile.subscription.status === 'future' &&
            //  //      response.profile.subscription.deleted === true) ||
            //  //  response.profile.subscription.status === 'cancelled'
            //  // )
            //  //  navigate('/subscription');
            //  // else navigate('/socialcalendar');
            // }
            navigate('/socialcalendar');
        } catch (error: any) {
            setAuthLoading(false);
            toast.error(error.message || 'Failed');
        }
    };

解决方案

问题1:/verify页面空白,组件未渲染

问题根源:/verify 嵌套在 PrivateRoute 下,未验证用户跳转至 /verify 时,PrivateRoute 会再次检测到用户未验证,重复跳转至 /verify,形成无限重定向循环,导致组件无法渲染。

修复步骤:

  1. 将 /verify 路由从 PrivateRoute 嵌套中移出,单独配置专属路由组件
  2. 调整路由权限逻辑,区分「已登录未验证用户」和「已验证用户」的访问权限

修改后的路由配置:

<BrowserRouter>
  <Routes>
    {/* 公共路由保持不变 */}
    <Route path='/login' element={<PublicRoute />}>
      <Route path='/login' element={<Login />} />
    </Route>
    <Route path='/register' element={<PublicRoute />}>
      <Route path='/register' element={<Register />} />
    </Route>
    <Route path='/reset-password' element={<PublicRoute />}>
      <Route path='/reset-password' element={<ResetPassword />} />
    </Route>
    <Route path='/forgot-password' element={<PublicRoute />}>
      <Route path='/forgot-password' element={<ForgetPassword />} />
    </Route>

    {/* 新增:仅允许已登录未验证用户访问的路由 */}
    <Route path='/verify' element={<VerifyRoute />}>
      <Route index element={<Verify />} />
    </Route>

    {/* 私有路由:仅允许已验证用户访问 */}
    <Route path='/' element={<PrivateRoute />}>
      <Route path='/' element={<Main />}>
        <Route path='/profile' element={<Profile />} />
        <Route path='/socialcalendar' element={<SocialCalendar />} />
        <Route path='/subscription' element={<Subscription />} />
        <Route index element={<Navigate to='/socialcalendar' />} />
      </Route>
    </Route>

    <Route path='*' element={<Navigate to='/login' />} />
  </Routes>
</BrowserRouter>

新增 VerifyRoute 组件:

import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';

const VerifyRoute = () => {
  const isLoggedIn = useSelector((state: any) => state.auth.authentication);
  const isVerified = isLoggedIn?.profile?.verified;

  if (!isLoggedIn) return <Navigate to='/login' replace />;
  if (isVerified) return <Navigate to='/socialcalendar' replace />;
  return <Outlet />;
};

export default VerifyRoute;

调整原 PrivateRoute 逻辑:

const PrivateRoute = () => {
    const isLoggedIn = useSelector((state: any) => state.auth.authentication);
    const isVerified = isLoggedIn?.profile?.verified;

    if (!isLoggedIn) return <Navigate to='/login' replace />;
    if (!isVerified) return <Navigate to='/verify' replace />;
    return <Outlet />;
};

问题2:登录逻辑需依赖setTimeout才能正确跳转

问题根源:Redux状态更新是异步操作,dispatch(setAuthentication(response)) 后状态不会立即同步,此时直接调用 navigate 会基于旧状态判断跳转目标。

修复步骤:
移除 setTimeout,直接使用登录接口返回的 response 数据进行判断,无需等待Redux状态更新:

const login = async (email: string, password: string) => {
    try {
        setAuthLoading(true);
        const response: any = await authLogin(email, password);
        dispatch(setAuthentication(response as any));
        toast.success('Login is succeeded!');
        setAuthLoading(false);
        
        // 直接用接口返回的数据判断验证状态
        if (!response.profile.verified) {
            navigate('/verify');
        } else {
            // 可在此添加订阅状态判断逻辑
            navigate('/socialcalendar');
        }
    } catch (error: any) {
        setAuthLoading(false);
        toast.error(error.message || 'Failed');
    }
};

内容的提问来源于stack exchange,提问作者Ayush Jaipuriyar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:07:02