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

如何用preact-router实现路由保护?地址栏访问受保护路由失效问题

Preact-Router 路由保护问题解决方案

问题描述

在组件层面结合AuthContext实现路由保护时,应用内导航访问受保护路由(如/cart)功能正常,但通过浏览器地址栏直接访问该路由时,跳转至/login的逻辑未触发,仅显示空白页面。

相关代码如下:

CartPage 组件

export const CartPage = (): JSX.Element => {
  const { isAuthenticated } = useAuthContext();

  return (
    <>
      {isAuthenticated ? (
        <Cart />
      ) : (
        (() => {
          route("/login");
        })()
      )}
    </>
  );
};

App 组件

export function App() {
  return (
    <div>
      <AuthProvider>
        <Navbar />

        <Layout>
          <ViewToggleProvider>
            <Router>
              <Route path="/" component={HomePage} />

              <AsyncRoute path="/login" component={LoginPage} />

              <AsyncRoute exact path="/cart" component={CartPage} />

              <AsyncRoute
                path="/orders/:orderId"
                component={OrderDetailsPage}
              />
            </Router>
          </ViewToggleProvider>
        </Layout>
      </AuthProvider>
    </div>
  );
}

原因分析

直接通过地址栏访问时,组件渲染阶段isAuthenticated可能还处于初始状态(比如用户认证信息是异步获取的),此时立即执行route()跳转,preact-router的路由初始化流程尚未完成,导致跳转逻辑失效。同时,组件返回空的<></>,所以页面显示空白。另外,在JSX渲染中直接执行立即执行函数属于副作用操作,违反Preact渲染纯函数原则,易引发不可预期行为。

解决方案

方案1:修复组件层面的路由保护逻辑

改用useEffect在组件挂载后执行跳转逻辑,确保路由环境已准备就绪,同时监听isAuthenticated的变化:

import { useEffect } from 'preact/hooks';
import { useNavigate } from 'preact/router'; // 推荐使用useNavigate替代直接调用route()

export const CartPage = (): JSX.Element => {
  const { isAuthenticated } = useAuthContext();
  const navigate = useNavigate();

  useEffect(() => {
    // 未认证时跳转至登录页
    if (!isAuthenticated) {
      navigate('/login');
    }
  }, [isAuthenticated, navigate]);

  // 未认证时返回加载状态,避免空白页面
  if (!isAuthenticated) {
    return <div>加载中...</div>;
  }

  return <Cart />;
};

方案2:路由层面统一实现保护(更推荐)

创建通用的ProtectedRoute组件,集中管理所有受保护路由,避免重复逻辑:

1. 实现ProtectedRoute组件

import { useEffect } from 'preact/hooks';
import { useNavigate } from 'preact/router';

export const ProtectedRoute = ({ children }) => {
  const { isAuthenticated } = useAuthContext();
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login');
    }
  }, [isAuthenticated, navigate]);

  if (!isAuthenticated) {
    return <div>加载中...</div>; // 可替换为自定义加载组件
  }

  return children;
};

2. 在App中使用ProtectedRoute

export function App() {
  return (
    <div>
      <AuthProvider>
        <Navbar />
        <Layout>
          <ViewToggleProvider>
            <Router>
              <Route path="/" component={HomePage} />
              <AsyncRoute path="/login" component={LoginPage} />
              
              {/* 保护购物车路由 */}
              <Route exact path="/cart">
                <ProtectedRoute>
                  <CartPage />
                </ProtectedRoute>
              </Route>
              
              {/* 保护订单详情路由 */}
              <Route path="/orders/:orderId">
                <ProtectedRoute>
                  <OrderDetailsPage />
                </ProtectedRoute>
              </Route>
            </Router>
          </ViewToggleProvider>
        </Layout>
      </AuthProvider>
    </div>
  );
}

若需结合AsyncRoute使用,可调整为:

<AsyncRoute exact path="/cart" component={() => (
  <ProtectedRoute>
    <CartPage />
  </ProtectedRoute>
)} />

方案对比

  • 组件层面修复:适合单个路由的简单保护,但逻辑分散,维护成本高。
  • 路由层面实现:集中管理所有受保护路由,逻辑统一,便于后续扩展(如添加权限校验、统一加载状态处理等),是更优的实践方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 16:45:21