React Router 7 全局路由保护

之前项目的路由保护是这样的:每个需要登录的页面都判断一下 token,没有就跳转登录页。

javascript 复制代码
// 之前:每个页面都要写
const SomePage = () => {
  const token = localStorage.getItem('token');
  if (!token) {
    return <Navigate to="/login" />;
  }
  
  return <div>页面内容</div>;
};

这样写有几个问题:

  1. 每个页面都要重复这个逻辑
  2. 万一漏了一个页面,就有安全漏洞
  3. 代码很乱,业务逻辑混在一起

后来我改成全局路由保护,清爽多了。

React Router 7 的新特性

React Router 7 推出了 <BrowserRouter>basename 和一些新特性,但最实用的是嵌套路由。

但我的方案不是在每个路由外面套 <ProtectedRoute>,而是在 main.jsx 里套一个全局的:

javascript 复制代码
// main.jsx
<BrowserRouter>
  <ProtectedRoute>  {/* 全局保护,只套一次 */}
    <ConfigProvider>
      <App />
    </ConfigProvider>
  </ProtectedRoute>
</BrowserRouter>

这样 <App /> 里面的所有路由都会经过保护逻辑。

ProtectedRoute 的实现

ProtectedRoute 组件要做三件事:

  1. 检查 token,没有就跳登录
  2. 检查路由是否合法(防止访问不存在的页面)
  3. 处理登录后跳回原页面的逻辑
javascript 复制代码
const ProtectedRoute = ({ children }) => {
  const location = useLocation();
  const pathname = location.pathname;
  
  // 1. 检查路由是否合法
  const allowedRoutes = [
    "/login", "/", "/chat", "/customer", "/review-dashboard", "/pdf-annotator/:id", ...
  ];
  
  const isValidRoute = allowedRoutes.some(route =>
    pathname === route || pathname.startsWith(`${route}/`)
  );
  
  if (!isValidRoute) {
    return <Navigate to="/404" replace />;
  }
  
  // 2. 登录页面直接放行
  if (pathname === "/login") {
    return <>{children}</>;
  }
  
  // 3. 检查 token
  const token = localStorage.getItem("token");
  if (!token) {
    localStorage.setItem("pathname", pathname); // 保存原路径
    return <Navigate to="/login" replace />;
  }
  
  // 4. 根路径重定向
  if (pathname === "/") {
    return <Navigate to="/chat" replace />;
  }
  
  return <>{children}</>;
};

这样写的好处:

  • 所有路由都在一个地方管理,不会漏
  • 未定义的路由自动跳 404
  • 登录后自动跳回原页面
但是有个坑:401 错误处理

用户登录后,token 会过期。这时候后端返回 401,我需要自动跳转到登录页。

但这个逻辑不能写在 ProtectedRoute 里,因为它只在路由切换时执行,不会响应 API 请求。

我把它写在了 Axios 拦截器里:

javascript 复制代码
// request.js
export let isRelogin = { show: false };

request.interceptors.response.use(
  (response) => {
    if (response.data.code === 401) {
      if (!isRelogin.show) {
        isRelogin.show = true;
        message.error('登录状态已过期');
        localStorage.setItem("pathname", window.location.pathname);
        localStorage.removeItem("token");
        window.location.href = "/login";
      }
    } else if (response.data.code !== 200) {
      message.error(response.data.msg);
      return Promise.reject(response.data);
    }
    return response;
  },
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem("token");
      window.location.href = "/login";
    }
    return Promise.reject(error);
  }
);

这里有个坑:如果多个请求同时返回 401,会弹出多次错误提示。

我用了个 isRelogin.show 标志位,确保只弹一次:

javascript 复制代码
export let isRelogin = { show: false };

if (response.data.code === 401) {
  if (!isRelogin.show) {  // 只处理第一次
    isRelogin.show = true;
    message.error('登录状态已过期');
    window.location.href = "/login";
  }
}

登录成功后,记得重置这个标志位:

javascript 复制代码
// 登录成功后
isRelogin.show = false;
懒加载怎么处理?

React Router 7 推荐用懒加载,但 ProtectedRoute 会阻止懒加载的组件渲染。

我的方案是:只懒加载页面组件,不懒加载 ProtectedRoute

javascript 复制代码
// App.jsx
const HomePage = lazy(() => import("./view/HomePage"));
const PDFAnnotatorDemo = lazy(() => import("./view/PDFAnnotatorDemo"));

const App = () => {
  return (
    <Suspense fallback={<PageLoading />}>
      <Routes>
        <Route path="/homepage" element={<HomePage />} />
        <Route path="/pdf-annotator/:id" element={<PDFAnnotatorDemo />} />
        ...
      </Routes>
    </Suspense>
  );
};

ProtectedRoutemain.jsx 里,不会被懒加载,所以一开始就会加载。

最后的效果

现在的路由架构:

  • main.jsx:全局保护 + 登录页不懒加载
  • App.jsx:懒加载所有其他页面
  • request.js:401 自动跳登录

代码清爽多了,也不用担心漏保护某个页面。

几个踩坑总结
  1. 全局保护比单独保护好 :一次套在 main.jsx 里就行
  2. 401 处理要防重复 :用 isRelogin.show 标志位
  3. 路由白名单要维护:未定义的路由跳 404
  4. 懒加载不能保护 ProtectedRoute:它要最先加载
  5. 登录后要跳回原路径 :用 localStorage.pathname 保存
相关推荐
yuhaiqiang7 分钟前
为什么这道初中数学题击溃了所有 AI
前端·后端·面试
djk88889 分钟前
支持手机屏幕的layui后台html模板
前端·html·layui
紫_龙11 分钟前
最新版vue3+TypeScript开发入门到实战教程之watch详解
前端·javascript·typescript
默默学前端41 分钟前
ES6模板语法与字符串处理详解
前端·ecmascript·es6
lxh01131 小时前
记忆函数 II 题解
前端·javascript
我不吃饼干1 小时前
TypeScript 类型体操练习笔记(三)
前端·typescript
华仔啊1 小时前
除了防抖和节流,还有哪些 JS 性能优化手段?
前端·javascript·vue.js
CHU7290351 小时前
随时随地学新知——线上网课教学小程序前端功能详解
前端·小程序
清粥油条可乐炸鸡1 小时前
motion入门教程
前端·css·react.js
这是个栗子1 小时前
【Vue3项目】电商前台项目(四)
前端·vue.js·pinia·表单校验·面包屑导航