react-router

SKILL.md

React Router

Act as a senior React Router engineer reviewing code for correctness, performance, and best practices.

Review: $ARGUMENTS

ReactRouter { Loaders { Fetch all data in loaders, never in components with useEffect. Loaders run before render and eliminate loading spinners. Use Promise.all for independent data fetches within a loader to avoid sequential waterfalls. Use request-scoped caching (via context) so multiple loaders can call the same data function without duplicate requests. Control revalidation with shouldRevalidate to skip unnecessary refetches. Use useRevalidator for polling/focus patterns. Type loaders with Route.LoaderArgs and consume with useLoaderData<typeof loader>(). Validate URL params early with zod or invariant — don't trust params to be well-formed. Pass request.signal to fetch calls and database queries so they abort when the user navigates away. Colocate data queries in queries.server.ts next to the route file. }

Actions { Validate all form data with zod schemas. Return validation errors with data({ errors }, { status: 400 }) — don't throw for validation failures. Use throw redirect("/path") after successful mutations to prevent resubmission (Post/Redirect/Get). Use zod .transform() for input sanitization (trim, lowercase, parse numbers) during validation. Use z.discriminatedUnion("intent", [...]) to handle multiple actions in one route with type-safe intent matching. Use clientAction for instant client-side validation before hitting the server. Re-throw redirects and unknown errors in catch blocks — only catch expected error types. }

Forms { Use useFetcher for in-place mutations (likes, toggles, inline edits) that shouldn't trigger navigation. Use <Form> when the mutation should navigate. Show pending state with fetcher.state !== "idle" or useNavigation().state. Use useSpinDelay to avoid flicker. Reset uncontrolled forms on success with formRef.current?.reset() in an effect. Return field values from actions on validation error so inputs repopulate with defaultValue={actionData?.fields?.email}. Add <HoneypotInputs /> to public-facing forms for bot protection. }

Routing { Organize routes as folders with colocated queries.server.ts, actions.server.ts, route.tsx, and components/. Use resource routes (no default export) for API-like endpoints. Name them api.<resource>.tsx. Use dedicated action routes (actions.<noun>-<verb>.ts) for reusable mutations consumed by multiple pages via useFetcher. Name the default export Component in all route files. Never import from other route files — import shared modules instead. Exception: import type { action } for useFetcher type inference. Access parent route data with useRouteLoaderData in UI. In loaders, re-fetch (request-scoped caching prevents duplicate calls). }

Middleware { Authenticate in middleware, authorize in each loader/action. Keep auth checks close to data access. Store session in middleware so loaders/actions get a single instance per request. Use AsyncLocalStorage for request-scoped context accessible without passing args through every function. Add Server-Timing headers in middleware for performance observability. Generate a request ID in middleware for log correlation across the request lifecycle. }

Security { Protect mutations with CSRF tokens or verify Sec-Fetch-Site headers to reject cross-site requests. Sanitize user-driven redirect URLs with safeRedirect(redirectTo, "/") — never redirect to arbitrary user input. Apply CORS headers only to API resource routes that need cross-origin access. Use specific origins, not wildcards. Validate cookie payloads with schemas using typed cookies. Use prefetch="intent" on <Link> for faster navigation — preloads data on hover/focus. }

ErrorHandling { Export ErrorBoundary from every route with data fetching. Use isRouteErrorResponse to distinguish HTTP errors from unexpected exceptions. In layout routes, make ErrorBoundary layout-aware so errors render within the app shell. Use <Suspense> with <Await resolve={promise}> for streamed loader data — return promises from data() and they auto-stream. } }

Weekly Installs
12
First Seen
Feb 10, 2026
Installed on
claude-code11
opencode4
gemini-cli4
codebuddy4
github-copilot4
codex4