About react-web-advanced
react-web-advanced provides implementation patterns for React web apps using TanStack Router, TanStack Start, and TanStack Virtual. It is for web developers and covers routing, server rendering, data loading, and large lists, with guidance on working alongside React Query.
Key features
- 01TanStack Router routing and URL state
- 02TanStack Start server functions and SSR
- 03TanStack Virtual for large lists
- 04Route loaders integrated with React Query
- 05Code splitting and Suspense patterns
Use cases
- 01Implement type-safe navigation and search parameters
- 02Fetch and prefetch data during route loading
- 03Render lists containing many items
- 04Configure SSR and authenticated route flows
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill react-web-advancedAfter installing, type/react-web-advancedin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/better-auth-start.mdintegration.mdrouter.mdssr-auth.mdstart.mdtesting.mdvirtual.mdDetails
From the project READMEReact Web Advanced: TanStack Router, Start & Virtual
Web-specific patterns for React apps built on the TanStack Router + Start + Virtual stack.
This skill extends react-advanced (core cross-platform patterns). Read that skill first for
React Query, XState, Zustand, Zod, TanStack Form, and TanStack Table conventions.
Web Architecture
The web stack adds three layers on top of the shared core:
| Layer | Library | Responsibility |
|---|---|---|
| Routing + URL state | TanStack Router | Type-safe navigation, search params, route loaders |
| Full-stack boundary | TanStack Start | Server functions (createServerFn), SSR, streaming |
| Large lists | TanStack Virtual | Virtualized rendering for 1000+ items |
The golden rule: queryOptions as single source of truth
Define query options once, import everywhere — loaders, components, invalidation:
// queries/posts.ts
export const postsQueryOptions = queryOptions({
queryKey: ["posts"],
queryFn: fetchPosts,
staleTime: 30_000,
});
Router + React Query wiring
The router receives QueryClient as context — the single integration point:
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 is intentional — without it, the router caches loader results
independently, causing React Query's staleTime to be ignored during preloads.
Route Loader + React Query Pattern
ensureQueryData for blocking, prefetchQuery for non-blocking
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>
}
Avoid waterfall requests
Prefetch all independent data in route loaders using 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));
};
- Never fetch data in
useEffectthat could go in a route loader - Parent and child route loaders run concurrently by default
Performance Patterns
React Compiler (React 19+)
With the compiler enabled:
- Do not manually wrap components in
React.memo - Do not manually use
useMemo/useCallbackfor performance - Do write idiomatic React — the compiler handles memoization
- Do ensure code follows Rules of React (no mutation during render)
Manual useMemo/useCallback remain useful only for controlling effect dependencies.
Suspense boundaries placement
- Route-level boundaries: use
pendingComponent/errorComponenton route definitions - Within routes: wrap non-blocking data in
<Suspense>individually - Group co-dependent queries under one
<Suspense>so they resolve together - Independent queries get separate
<Suspense>boundaries
Code splitting
- Split routes using
.lazy()or.lazy.tsxfiles — critical config (loader, params) stays in the main file, component/UI splits into the lazy file - Use
React.lazyfor heavy on-demand components (rich editors, charts) - Machine definitions auto-split since they are separate
.tsfiles
File Organization
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
Key conventions:
- Route-specific components use
-prefix directories to avoid route tree inclusion - Pathless route groups
(name)/for organization without URL impact .lazy.tsxfiles export component/pendingComponent/errorComponent only- Co-locate test files next to source (
.test.ts/.test.tsx)
Common Pitfalls
-
Wrong property order in
createFileRoute— must bevalidateSearch -> loaderDeps -> beforeLoad -> loaderfor TypeScript inference. Install@tanstack/eslint-plugin-routerwithcreate-route-property-orderrule. -
Returning entire search in
loaderDeps— invalidates cache on any param change. Extract only the deps the loader uses. -
preload="intent"without React Query cache — preloaded data is discarded if user doesn't navigate. Combine with React Query'sensureQueryDatafor cache persistence. -
Not registering the router — without
declare module Register, everything isany. -
Forgetting
<Suspense>around<Await>— runtime error without a Suspense ancestor. -
defaultPreloadStaleTimenot set to 0 — Router's default staleTime overrides React Query's staleTime during preloads, causing stale data to be served. -
useLoaderDatainnotFoundComponent— not valid. UseRoute.useParams()or pass data viathrow notFound({ data: ... }). -
Building all pages before verifying auth flow — implement and verify the auth flow first (login → session cookie → protected route guard → redirect) end-to-end before building feature pages. Auth integration bugs hide behind each other (7+ layers deep). See
references/ssr-auth.md. -
defaultPreload: "intent"triggering auth checks on hover — with intent preloading,beforeLoadfires on every link hover. IfbeforeLoaddoes auth validation, every hover triggers a session check. Disable intent preloading during auth debugging, or make auth checks cache-aware (queryClient.getQueryData(sessionKey)before fetching). -
Blanket
invalidateQueries()cascading through auth hooks —queryClient.invalidateQueries()with no key filter invalidates everything, including session queries. Auth reactive hooks refetch, components re-render,beforeLoadre-runs. Always scope invalidation to the specific entity query key. Seereferences/integration.mdfor query key namespacing patterns. -
Auth nanostores re-rendering on every API mutation — libraries like Better Auth use nanostores internally. Every successful mutation toggles signals, causing all
useSession()/useActiveOrganization()subscribers to re-render synchronously. Combined with TanStack Router, this creates infinite re-render loops. Replace auth hooks with React Query wrappers. Seereferences/better-auth-start.md. -
beforeLoadre-runs on every client-side navigation — if the session check insidebeforeLoadcalls a server function without React Query caching, every link click triggers a server round-trip. UseensureQueryDatawithstaleTimeto serve from cache. Seereferences/ssr-auth.md.
Reference Files
Read the relevant reference file when working with a specific library:
| File | When to read |
|---|---|
references/router.md | Routing, search params, loaders, code splitting, navigation |
references/start.md | Server functions, SSR, middleware, deployment |
references/virtual.md | Virtualization, dynamic heights, infinite scroll, grids |
references/integration.md | Router+Query wiring, auth guards, query key namespacing, org data |
references/ssr-auth.md | SSR cookie auth, Vite proxy, CORS, route guards, FOUC, CF Workers |
references/better-auth-start.md | Better Auth + TanStack Start: signals, RQ wrapper, org state |
references/testing.md | Testing Router routes, renderWithProviders |