关于 react-web-advanced
react-web-advanced 为基于 TanStack Router、TanStack Start 和 TanStack Virtual 的 React Web 应用提供实现模式,面向构建 Web 前端的开发者。内容涵盖路由、服务端渲染、数据加载和大型列表,并强调与 React Query 的协作方式。
主要功能
- 01TanStack Router 路由与 URL 状态
- 02TanStack Start 服务端函数与 SSR
- 03TanStack Virtual 虚拟化大型列表
- 04路由加载器与 React Query 集成
- 05代码拆分和 Suspense 模式
适用场景
- 01实现类型安全的导航和搜索参数
- 02在路由加载期间获取和预取数据
- 03渲染包含大量条目的列表
- 04配置 SSR 与认证路由流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill react-web-advanced安装后在 Claude Code 中输入/react-web-advanced确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/better-auth-start.mdintegration.mdrouter.mdssr-auth.mdstart.mdtesting.mdvirtual.md详细介绍
React Web Advanced:TanStack Router、Start 与 Virtual
基于 TanStack Router + Start + Virtual 技术栈构建的 React 应用的 Web 专属模式。
此技能扩展了 react-advanced(跨平台核心模式)。请先阅读该技能,了解 React Query、XState、Zustand、Zod、TanStack Form 和 TanStack Table 的约定。
Web 架构
Web 技术栈在共享核心之上增加了三个层:
| 层级 | 库 | 职责 |
|---|---|---|
| 路由 + URL 状态 | TanStack Router | 类型安全的导航、搜索参数、路由加载器 |
| 全栈边界 | TanStack Start | 服务端函数(createServerFn)、SSR、流式传输 |
| 大型列表 | TanStack Virtual | 为 1000+ 个项目进行虚拟化渲染 |
黄金法则:queryOptions 是唯一真实来源
只定义一次查询选项,然后在加载器、组件和缓存失效处理中统一导入:
// queries/posts.ts
export const postsQueryOptions = queryOptions({
queryKey: ["posts"],
queryFn: fetchPosts,
staleTime: 30_000,
});
Router + React Query 接线
Router 将 QueryClient 接收为上下文——这是唯一的集成点:
const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: "intent",
defaultPreloadStaleTime: 0, // Let React Query manage staleness
});
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
defaultPreloadStaleTime: 0 是有意设置的——若不设置,Router 会独立缓存加载器结果,导致预加载时 React Query 的 staleTime 被忽略。
路由加载器 + React Query 模式
阻塞式加载使用 ensureQueryData,非阻塞式加载使用 prefetchQuery
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ context: { queryClient }, params }) => {
// Fire-and-forget secondary data
queryClient.prefetchQuery(commentsQueryOptions(params.postId))
// Block route render until critical data is ready
await queryClient.ensureQueryData(postQueryOptions(params.postId))
},
component: PostDetail,
})
function PostDetail() {
const { postId } = Route.useParams()
// Data guaranteed in cache — instant, no loading state
const { data: post } = useSuspenseQuery(postQueryOptions(postId))
return <h1>{post.title}</h1>
}
避免请求瀑布
在路由加载器中使用 Promise.all 预取所有相互独立的数据:
loader: async ({ context: { queryClient }, params }) => {
await Promise.all([
queryClient.ensureQueryData(userQueryOptions(params.id)),
queryClient.ensureQueryData(permissionsQueryOptions(params.id)),
]);
// Fire-and-forget for non-critical
queryClient.prefetchQuery(activityQueryOptions(params.id));
};
- 绝不要在
useEffect中获取本可放入路由加载器的数据 - 默认情况下,父路由和子路由的加载器会并发运行
性能模式
React Compiler(React 19+)
启用编译器后:
- 不要手动用
React.memo包装组件 - 不要为了性能手动使用
useMemo/useCallback - 要编写符合惯例的 React 代码——编译器会处理记忆化
- 要确保代码遵循 React 规则(渲染期间不进行变更)
手动使用 useMemo / useCallback 仅在控制 effect 依赖项时仍有用。
Suspense 边界的位置
- 路由级边界:在路由定义中使用
pendingComponent/errorComponent - 路由内部:为非阻塞数据分别使用
<Suspense>包裹 - 将相互依赖的查询归入同一个
<Suspense>,以便它们一同完成 - 相互独立的查询使用单独的
<Suspense>边界
代码拆分
- 使用
.lazy()或.lazy.tsx文件拆分路由——关键配置(加载器、参数)保留在主文件中,组件 / UI 拆分到延迟加载文件中 - 对按需加载的重型组件(富文本编辑器、图表)使用
React.lazy - 机器定义会自动拆分,因为它们位于独立的
.ts文件中
文件组织
src/
routes/ # TanStack Router file-based routes
__root.tsx # Root layout, router context type
(auth)/ # Route group — no URL impact
(app)/
users/
$userId.tsx
$userId.lazy.tsx # Component-only code split
-components/ # "-" prefix excludes from route tree
queries/ # queryOptions definitions — one file per entity
mutations/ # useMutation wrappers
machines/ # XState machine definitions (pure TS, no React)
stores/ # Zustand stores
serverFns/ # TanStack Start server functions
components/
ui/ # Design system primitives
shared/ # Cross-feature shared components
lib/
query-client.ts # QueryClient singleton
router.ts # Router singleton
test/
setup.ts # Vitest setup
test-utils.tsx # renderWithProviders
mocks/handlers.ts # MSW handlers
主要约定:
- 路由专属组件使用
-前缀目录,避免纳入路由树 - 使用无路径路由组
(name)/进行组织,不影响 URL .lazy.tsx文件仅导出 component/pendingComponent/errorComponent- 测试文件与源文件放在一起(
.test.ts/.test.tsx)
常见陷阱
-
createFileRoute中属性顺序错误 — 为了 TypeScript 类型推断,顺序必须是validateSearch -> loaderDeps -> beforeLoad -> loader。 安装@tanstack/eslint-plugin-router,并启用create-route-property-order规则。 -
在
loaderDeps中返回完整的搜索参数 — 任何参数发生变化都会使缓存失效。 只提取 loader 使用的依赖项。 -
未配合 React Query 缓存使用
preload="intent"— 如果用户没有导航,预加载的数据就会被丢弃。 与 React Query 的ensureQueryData配合使用,以持久化缓存。 -
未注册路由器 — 如果没有
declare module Register,所有内容的类型都会变成any。 -
忘记在
<Await>外层包裹<Suspense>— 没有 Suspense 祖先组件时会出现运行时错误。 -
未将
defaultPreloadStaleTime设为 0 — 路由器的默认 staleTime 会在预加载期间覆盖 React Query 的 staleTime,导致返回过期数据。 -
在
notFoundComponent中使用useLoaderData— 这是无效的。请使用Route.useParams(),或 通过throw notFound({ data: ... })传递数据。 -
在验证认证流程前构建所有页面 — 应先端到端实现并验证认证流程(登录 → 会话 Cookie → 受保护路由守卫 → 重定向), 再构建功能页面。认证集成 Bug 会层层隐藏(深达 7 层以上)。 请参阅
references/ssr-auth.md。 -
defaultPreload: "intent"在悬停时触发认证检查 — 使用意图预加载时,每次悬停链接都会触发beforeLoad。如果beforeLoad执行认证验证,每次悬停都会触发会话检查。调试认证时请禁用意图预加载,或让认证 检查感知缓存(获取数据前先调用queryClient.getQueryData(sessionKey))。 -
无差别调用
invalidateQueries(),导致认证钩子级联触发 — 不带键过滤器调用queryClient.invalidateQueries()会使所有内容失效,包括 会话查询。认证响应式钩子会重新获取数据,组件会重新渲染,beforeLoad也会再次运行。 务必将失效范围限定为特定实体的查询键。 有关查询键命名空间模式,请参阅references/integration.md。 -
每次 API 变更都会导致认证 nanostores 重新渲染 — Better Auth 等库 内部使用 nanostores。每次成功的变更都会切换信号,导致所有
useSession()/useActiveOrganization()订阅者同步重新渲染。 与 TanStack Router 结合时,这会造成无限重新渲染循环。请用 React Query 包装器替换认证 钩子。请参阅references/better-auth-start.md。 -
每次客户端导航都会重新运行
beforeLoad— 如果beforeLoad中的会话检查调用了没有 React Query 缓存的服务器函数,每次点击链接都会触发一次服务器往返。 使用带有staleTime的ensureQueryData,从缓存中提供数据。请参阅references/ssr-auth.md。
参考文件
使用特定库时,请阅读相关参考文件:
| 文件 | 阅读时机 |
|---|---|
references/router.md | 路由、搜索参数、加载器、代码拆分、导航 |
references/start.md | 服务器函数、SSR、中间件、部署 |
references/virtual.md | 虚拟化、动态高度、无限滚动、网格 |
references/integration.md | 路由器与 Query 的连接、认证守卫、查询键命名空间、组织数据 |
references/ssr-auth.md | SSR Cookie 认证、Vite 代理、CORS、路由守卫、FOUC、CF Workers |
references/better-auth-start.md | Better Auth + TanStack Start:信号、RQ 包装器、组织状态 |
references/testing.md | 测试路由器路由、renderWithProviders |