在 React Router v6 中,若在根路径 / 的 loader 中无条件调用 redirect(),会导致路由反复触发加载 → 重定向 → 再加载的死循环;正确做法是将重定向逻辑移至 <Route index> 下的 <Navigate> 组件,并确保 loader 仅负责预取数据、不参与导航控制。
在 react router v6 中,若在根路径 `/` 的 `loader` 中无条件调用 `redirect()`,会导致路由反复触发加载 → 重定向 → 再加载的死循环;正确做法是将重定向逻辑移至 `
在使用 React Router v6 与 React Query(如 queryClient.ensureQueryData)协同开发时,一个常见误区是:试图在根路由的 loader 函数中同时完成数据预取和导航跳转。这不仅违反了 React Router 的设计原则,更会直接引发无限循环——因为每次重定向到 /workspaces/1 后,用户再返回 /(例如刷新或手动访问),loader 又会再次执行 redirect(),形成不可终止的加载-跳转链。
? 核心原则:Loader ≠ Navigator
React Router 的 loader 函数仅用于声明式数据获取,其返回值应为可序列化的数据(或 defer() / json() 响应),绝不应包含副作用性导航操作(如 redirect())。导航逻辑必须交由组件层(如 <Navigate>)或路由配置层(如 index 路由)处理。
✅ 正确实现方式
1. 重构 homeLoader:只预取,不跳转
// ✅ 正确:loader 返回 Promise<void> 或预取后的数据,不触发 redirect
const homeLoader = (queryClient: QueryClient) => () => {
return queryClient.ensureQueryData({
queryKey: ['someKey'],
queryFn: doSomething,
});
};注意:此处采用柯里化写法,使 queryClient 可安全注入(通常来自 React Query 上下文),同时返回符合 React Router 签名的 loader 函数(() => Promise<any>)。
2. 使用 index 路由 + <Navigate> 实现默认跳转
import {
createBrowserRouter,
createRoutesFromElements,
Route,
Navigate,
} from 'react-router-dom';
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="/"
element={<Home />}
loader={homeLoader(queryClient)} // ✅ 注入后直接传入 loader 属性
>
{/* ✅ 关键:用 index 路由定义 "/" 的默认行为 */}
<Route index element={<Navigate to="/workspaces/1" replace />} />
<Route path="workspaces" element={<Workspaces />}>
<Route path=":workspaceId" element={<Workspace />} />
</Route>
</Route>
)
);? replace: true 是推荐选项,它会替换当前历史记录而非新增,避免用户点击「返回」时卡在空白 / 页面。
3. (可选)在 <Home> 组件中读取预取数据
若需在 Home 组件中消费预取结果,可结合 useLoaderData() 或 useQueryClient().getQueryData():
function Home() {
const data = useLoaderData(); // 若 loader 显式 return 数据
// 或
const queryClient = useQueryClient();
const someData = queryClient.getQueryData(['someKey']);
return <div>Home loaded: {someData?.name}</div>;
}⚠️ 注意事项与避坑指南
- 禁止在 loader 中调用 redirect():v6 的 redirect() 仅应在 loader 内部同步抛出(如权限校验失败),且必须是最终返回值;无条件重定向会破坏路由状态机。
- 确保 queryClient 实例唯一且已初始化:在 loader 外部注入前,确认 queryClient 已通过 <QueryClientProvider> 提供。
- 避免重复预取:ensureQueryData 默认不会重复请求已存在的有效缓存;如需强制刷新,请显式传入 staleTime: 0。
- 服务端渲染(SSR)兼容性:若启用 SSR,需确保 ensureQueryData 在服务端也能安全执行(如检查 typeof window === 'undefined')。
✅ 总结
解决此类无限循环的根本,在于严格分离关注点:
? Loader 职责:声明式数据准备(ensureQueryData, fetch, json);
? Router 职责:结构化导航意图(<Route index> + <Navigate>);
? Component 职责:消费数据、响应交互、渲染 UI。
遵循这一分层,既能保障数据预取效率,又能杜绝路由失控风险,是构建健壮 React Router + React Query 应用的关键实践。










