执行契约
- 适用场景
- 一组 Router 页面必须在任何子 Loader 执行前要求有效登录会话。
- 不要使用
- 不要把客户端 beforeLoad 当作服务端资源授权,也不要只靠隐藏导航链接。
- 前置检查
- 确认认证状态如何进入类型化 Router Context、私有路由的共同父级以及允许的登录回跳目标。
- 验证结果
- 测试未登录直达、登录刷新、会话过期和恶意外部回跳,并证明子 Loader 与服务端入口都在各自边界拒绝未授权请求。
- 失败模式
- 若页面先请求私有数据再跳登录,门禁放得太低或异步认证未在父 beforeLoad 中完成;上移到保护子树的最近共同父级。
- 安全边界
- 验证 redirect 目标为本站安全路径,防止开放重定向;所有私有 Server Function、Server Route 和数据源必须重新鉴权与授权。
决策顺序
先确认认证状态如何进入 Router Context,再选择保护整棵子树的最近 pathless layout。父级 beforeLoad 在子级之前执行;无会话时 throw redirect,有会话时返回最小 user 上下文。不要在每个叶子路由复制检查。
最小实现
使用 beforeLoad 参数中的 location.href 记录真实目标。登录处理器只允许站内相对路径或经过同源验证的目标;外部 URL 必须回退到固定首页。
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.user) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
return { user: context.auth.user }
},
component: () => <Outlet />,
})验证矩阵
验证未登录直达不会运行子 Loader;登录用户刷新正常;会话过期后下一次导航回到登录;外部 redirect 被拒绝;服务端数据入口在绕过 UI 直接调用时仍独立返回未授权。