
React Router V7
- 51 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with frontend development tasks.
About
react-router-v7 is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted coding.
- react-router-v7
- Frontend Development
- AI-coding skill
React Router V7 by the numbers
- 51 all-time installs (skills.sh)
- Ranked #1,296 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill react-router-v7Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 51 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with frontend development tasks.
Files
@react-router/dev CLI
The React Router CLI, provided by @react-router/dev. Should be listed in devDependencies so it is not deployed to your server.
npx @react-router/dev -hAvailability: Framework Mode only.
詳細説明
react-router dev
Runs your app in development mode with HMR and Hot Data Revalidation (HDR), powered by Vite.
- HMR updates client-side code (components, markup, styles) without a full reload
- HDR updates server-side code — re-fetches loader data when server code changes, keeping the app in sync without a page refresh
react-router dev| Flag | Type | Description |
|---|---|---|
--port | number | Specify port |
--host | string | Specify hostname |
--open | `boolean \ | string` |
--strictPort | boolean | Exit if specified port is already in use |
--cors | boolean | Enable CORS |
--force | boolean | Force optimizer to ignore cache and re-bundle |
--mode, -m | string | Set env mode |
--config, -c | string | Use specified config file |
--logLevel, -l | string | Log level: info, warn, error, silent |
--clearScreen | boolean | Allow/disable clear screen when logging |
--profile | — | Start built-in Node.js inspector |
---
react-router build
Builds the app for production with Vite. Sets NODE_ENV=production and minifies output.
react-router build| Flag | Type | Default | Description |
|---|---|---|---|
--minify | `boolean \ | "terser" \ | "esbuild"` |
--sourcemapClient | `boolean \ | "inline" \ | "hidden"` |
--sourcemapServer | `boolean \ | "inline" \ | "hidden"` |
--assetsInlineLimit | number | 4096 | Static asset base64 inline threshold (bytes) |
--mode, -m | string | — | Set env mode |
--config, -c | string | — | Use specified config file |
--logLevel, -l | string | — | Log level |
--clearScreen | boolean | — | Allow/disable clear screen |
--emptyOutDir | boolean | — | Force empty outDir when outside root |
--profile | — | — | Start built-in Node.js inspector |
---
react-router reveal
Generates entry.client.tsx and entry.server.tsx in your app directory, giving you control over entry points. When these files exist, React Router uses them instead of the built-in defaults.
npx react-router reveal| Flag | Type | Default | Description |
|---|---|---|---|
--typescript | boolean | true | Generate TypeScript files |
--no-typescript | boolean | false | Generate plain JavaScript files |
--mode, -m | string | — | Set env mode |
--config, -c | string | — | Use specified config file |
---
react-router routes
Prints the route tree to the terminal. Useful for inspecting route config output.
react-router routes
# JSON output
react-router routes --json| Flag | Type | Default | Description |
|---|---|---|---|
--json | boolean | false | Output routes in JSON format |
--mode, -m | string | — | Set env mode |
--config, -c | string | — | Use specified config file |
---
react-router typegen
Generates TypeScript types for all routes into .react-router/types/. Runs automatically during react-router dev, but can be run manually for CI.
# Generate once
react-router typegen
# Watch mode
react-router typegen --watch| Flag | Type | Default | Description |
|---|---|---|---|
--watch | boolean | false | Watch for route config changes |
--mode, -m | string | — | Set env mode |
--config, -c | string | — | Use specified config file |
注意点
@react-router/devshould be indevDependencies, notdependencies, to avoid deploying dev tooling to your serverreact-router devrequires a Vite-based setup (Framework Mode); it is not available in Data or Declarative modes- Run
react-router typegenin CI beforetscto ensure generated types are present react-router revealonly needs to be run once; the generated entry files are then committed to your repo
関連
- Type Safety
- Hot Module Replacement
- Code Splitting
CLI
| Name | Description | Path |
|---|---|---|
| @react-router/dev CLI | The React Router CLI, provided by @react-router/dev. Should be… | dev.md |
Await
Renders a deferred (unawaited) Promise returned from a loader, providing automatic error handling. Must be wrapped in a <React.Suspense> boundary to show a loading fallback.
Props
| Name | Type | Description |
|---|---|---|
resolve | Promise<Resolve> | The Promise to resolve. Typically an unawaited value from useLoaderData(). |
children | `ReactNode \ | (resolvedValue: Resolve) => ReactNode` |
errorElement | ReactNode | Rendered when the Promise rejects. Use useAsyncError() inside it for the rejection value. |
使用例
import { Await, useLoaderData } from "react-router";
export async function loader() {
const reviews = getReviews(); // not awaited — returns a Promise
const book = await getBook();
return { book, reviews };
}
export default function Book() {
const { book, reviews } = useLoaderData();
return (
<React.Suspense fallback={<p>Loading reviews…</p>}>
<Await
resolve={reviews}
errorElement={<p>Could not load reviews.</p>}
>
{(resolvedReviews) => <Reviews items={resolvedReviews} />}
</Await>
</React.Suspense>
);
}注意点
- Must be wrapped in
<React.Suspense>— the Suspense boundary provides the fallback UI while the Promise is pending. - Unawaited loader Promises enable streaming (data arrives after initial HTML). Awaited Promises block navigation until resolved.
- If
errorElementis omitted, a rejected Promise bubbles to the nearest route-levelErrorBoundary(accessible viauseRouteError()). - Available in Framework and Data modes; not available in Declarative mode.
関連
- Outlet.md
Form
A progressively enhanced HTML <form> that submits data to route actions via fetch, activating pending states in useNavigation. After submission completes, all page data is automatically revalidated.
Props
| Name | Type | Description |
|---|---|---|
action | string | URL to submit form data to. Defaults to the closest route in context. |
method | `"get" \ | "post" \ |
encType | string | Encoding type: "application/x-www-form-urlencoded" (default), "multipart/form-data", or "text/plain". |
navigate | boolean | When false, submits via fetcher internally instead of navigating. |
fetcherKey | string | Specific fetcher key when using navigate={false} to pick up fetcher state elsewhere. |
replace | boolean | Replaces current history entry instead of adding a new one. |
state | any | State object to add to the History stack entry. |
preventScrollReset | boolean | Prevents scroll reset to top on navigation completion. |
relative | `"route" \ | "path"` |
reloadDocument | boolean | Forces full document navigation instead of client-side routing. |
viewTransition | boolean | Enables View Transition API for this navigation. |
discover | `"render" \ | "none"` |
onSubmit | (e: React.FormEvent) => void | Callback on form submission. Call e.preventDefault() to cancel. |
defaultShouldRevalidate | boolean | Specifies default revalidation behavior after submission. |
使用例
import { Form } from "react-router";
function NewEvent() {
return (
<Form action="/events" method="post">
<input name="title" type="text" />
<input name="description" type="text" />
<button type="submit">Create</button>
</Form>
);
}注意点
- Works as a plain HTML form before JavaScript loads (progressive enhancement).
- Best for submissions that should change the URL or add browser history entries. Use
<fetcher.Form>for submissions that should not affect History. - Native HTML forms only support
"get"and"post"; avoid other verbs if progressive enhancement is required. - After the action completes, all page data revalidates automatically to keep the UI in sync.
関連
- Link.md
- ScrollRestoration.md
Link
A progressively enhanced <a href> wrapper that enables client-side navigation. Supports prefetching, scroll management, view transitions, and relative path resolution.
Props
| Name | Type | Description |
|---|---|---|
to | `string \ | Partial<Path>` |
relative | `"route" \ | "path"` |
replace | boolean | Replaces current History entry instead of pushing a new one. |
state | any | Persistent client-side state passed to the next location via history.state. |
reloadDocument | boolean | Uses document navigation instead of client-side routing. |
preventScrollReset | boolean | Prevents scroll reset to top when clicked (requires <ScrollRestoration>). |
viewTransition | boolean | Enables View Transition API for the navigation. |
prefetch | `"none" \ | "intent" \ |
discover | `"render" \ | "none"` |
mask | string | Masked URL path for contextual SPA/SSR navigations. Framework mode only. |
defaultShouldRevalidate | boolean | Default revalidation behavior for the navigation. |
使用例
import { Link } from "react-router";
// Basic
<Link to="/dashboard">Dashboard</Link>
// With prefetch on hover
<Link to="/profile" prefetch="intent">Profile</Link>
// With state
<Link to="/somewhere" state={{ from: "home" }}>Go</Link>
// Relative to URL path (not route pattern)
<Link to=".." relative="path">Back</Link>注意点
prefetchinserts<link rel="prefetch">tags after the anchor element. If usingnav :last-childCSS selectors, switch tonav :last-of-typeto avoid broken styles.preventScrollResetonly suppresses scroll reset for new navigations; back/forward navigation still restores scroll normally.stateis client-side only (stored inhistory.state) and is not accessible on the server.maskonly works in SPA mode and SSR renders; sharing a masked URL loads only the masked location without context.
関連
- NavLink.md
- Form.md
- ScrollRestoration.md
Links
Renders all <link> tags collected from each route module's links export. Must be placed inside the <head> of the root document.
Props
| Name | Type | Description |
|---|---|---|
nonce | string | A nonce attribute applied to each rendered <link> element (for CSP). |
crossOrigin | string | A crossOrigin attribute applied to each rendered <link> element. |
使用例
import { Links } from "react-router";
export default function Root() {
return (
<html>
<head>
<Links />
</head>
<body>...</body>
</html>
);
}注意点
- Framework mode only — not available in Data or Declarative modes.
- Must be rendered inside
<head>. Placing it elsewhere will result in invalid HTML. - Works in conjunction with the
linksexport of each route module to inject stylesheets and other link tags.
関連
- Meta.md
- Scripts.md
- ScrollRestoration.md
Meta
Renders all <meta> tags collected from each route module's meta export. Must be placed inside the <head> of the root document.
Props
This component accepts no props.
使用例
import { Meta } from "react-router";
export default function Root() {
return (
<html>
<head>
<Meta />
</head>
<body>...</body>
</html>
);
}注意点
- Framework mode only — not available in Data or Declarative modes.
- Must be placed inside
<head>for correct HTML structure. - Automatically collects and renders all
<meta>tags from route modulemetaexports across the matched route hierarchy, including<title>,description, Open Graph tags, etc.
関連
- Links.md
- Scripts.md
Navigate
A component wrapper around useNavigate for use in React class components where hooks are not available. Triggers an immediate navigation when rendered.
Props
| Name | Type | Description |
|---|---|---|
to | `string \ | Path` |
replace | boolean | Replaces the current History entry instead of pushing a new one. |
state | any | State to pass to the new location, stored in history.state. |
relative | RelativeRoutingType | How to interpret relative routing in the to prop ("route" or "path"). |
使用例
import { Navigate } from "react-router";
// Inside a class component render method
if (!isLoggedIn) {
return <Navigate to="/login" replace />;
}注意点
- Designed specifically for React class components where hooks cannot be called. Prefer
useNavigatein function components. - Renders
null— it has no visible output; it only performs the navigation as a side effect. - Available in all three modes: Framework, Data, and Declarative.
関連
- Link.md
- Form.md
NavLink
A <Link> wrapper that automatically applies active/pending CSS classes and aria-current="page" based on whether its route is currently active. Supports render-function children and styles for dynamic styling.
Props
| Name | Type | Description |
|---|---|---|
to | `string \ | Partial<Path>` |
end | boolean | Match only when the URL ends at to (prevents parent matching child routes). |
caseSensitive | boolean | Makes URL matching case-sensitive. Default: false. |
className | `string \ | (props: NavLinkRenderProps) => string` |
style | `CSSProperties \ | (props: NavLinkRenderProps) => CSSProperties` |
children | `ReactNode \ | (props: NavLinkRenderProps) => ReactNode` |
replace | boolean | Replaces history entry instead of pushing. |
state | any | Persistent client-side navigation state. |
relative | `"route" \ | "path"` |
reloadDocument | boolean | Use browser navigation instead of client-side routing. |
preventScrollReset | boolean | Prevent scroll reset on navigation. |
viewTransition | boolean | Enable View Transition API for navigation. |
prefetch | `"none" \ | "intent" \ |
discover | `"render" \ | "none"` |
NavLinkRenderProps
| Name | Type | Description |
|---|---|---|
isActive | boolean | Whether the link's route is currently active. |
isPending | boolean | Whether a navigation to this route is pending (Framework & Data modes only). |
isTransitioning | boolean | Whether a view transition is currently in progress. |
使用例
import { NavLink } from "react-router";
<NavLink
to="/messages"
className={({ isActive, isPending }) =>
isPending ? "pending" : isActive ? "active" : ""
}
>
Messages
</NavLink>注意点
- Automatically adds
active,pending, andtransitioningclasses; override via theclassNamerender function. <NavLink to="/">matches every URL by default. Theendprop does not fully constrain root-path matching.isPendingis only available in Framework and Data modes, not in Declarative mode.- When using
prefetch,<link rel="prefetch">tags are inserted after the element — usenav :last-of-typeinstead ofnav :last-childin CSS.
関連
- Link.md
- ScrollRestoration.md
Outlet
Renders the matching child route element inside a parent route's component. Returns null when no child route matches the current URL.
Props
| Name | Type | Description |
|---|---|---|
context | any | Optional context value provided to the entire element tree below the outlet. Consumed via useOutletContext(). |
使用例
import { Outlet } from "react-router";
export default function DashboardLayout() {
return (
<div>
<nav>...</nav>
<Outlet />
</div>
);
}// Passing context to child routes
<Outlet context={{ user }} />
// In a child route component
import { useOutletContext } from "react-router";
const { user } = useOutletContext();注意点
- Only one
Outletshould be rendered per route level in the component tree. - Returns
null(renders nothing) when no child route matches — this is intentional and safe. - Available in all three modes: Framework, Data, and Declarative.
関連
- Form.md
PrefetchPageLinks
Renders <link rel="prefetch"> and <link rel="modulepreload"> tags for all modules and data of a target page, enabling instant navigation. Used internally by <Link prefetch> but can be rendered standalone.
Props
| Name | Type | Description |
|---|---|---|
page | string | Absolute path of the page to prefetch (e.g., "/dashboard"). |
...linkProps | object | Additional HTML <link> attributes (e.g., crossOrigin, integrity, rel) spread onto rendered tags. |
使用例
import { PrefetchPageLinks } from "react-router";
// Prefetch a page as the user types in a search field
function SearchBar({ query }) {
const topResult = useTopSearchResult(query);
return (
<>
<input value={query} onChange={...} />
{topResult && <PrefetchPageLinks page={topResult.href} />}
</>
);
}注意点
- Framework mode only — not available in Data or Declarative modes.
pagemust be an absolute path (starts with/).<Link prefetch="intent|render|viewport">uses this component internally. UsePrefetchPageLinksdirectly only when you need custom prefetch timing beyond whatLinkprovides.
関連
- Link.md
- Links.md
Components
| Name | Description | Path |
|---|---|---|
| Await | Renders a deferred (unawaited) Promise with automatic error handling… | ./Await.md |
| Form | Progressively enhanced <form> that submits data to route actions… | ./Form.md |
| Link | Progressively enhanced <a href> wrapper enabling client-side navigation… | ./Link.md |
| Links | Renders all <link> tags from route module links exports… | ./Links.md |
| Meta | Renders all <meta> tags from route module meta exports… | ./Meta.md |
| Navigate | Component wrapper of useNavigate for use in class components… | ./Navigate.md |
| NavLink | <Link> variant with auto-applied active/pending classes… | ./NavLink.md |
| Outlet | Renders the matched child route inside a parent route layout… | ./Outlet.md |
| PrefetchPageLinks | Renders <link rel="prefetch"> tags for a target page… | ./PrefetchPageLinks.md |
| ScrollRestoration | Emulates browser scroll restoration on navigation… | ./ScrollRestoration.md |
| Scripts | Renders the client-side JavaScript runtime of the app… | ./Scripts.md |
Scripts
Renders the client-side JavaScript runtime of the app. Must be placed inside <body>. Can be omitted to ship a JavaScript-free, traditional web app when server-rendering.
Props
| Name | Type | Description |
|---|---|---|
...scriptProps | ScriptsProps | Any valid <script> element attributes (e.g., nonce, crossOrigin) spread onto each rendered <script> tag. |
使用例
import { Scripts } from "react-router";
export default function Root() {
return (
<html>
<head>...</head>
<body>
<Scripts />
</body>
</html>
);
}// With CSP nonce
<Scripts nonce={cspNonce} />注意点
- Framework mode only — not available in Data or Declarative modes.
- Must be rendered inside
<body>, not<head>. - Omitting
<Scripts>in an SSR setup produces a fully functional app with no JavaScript (progressive enhancement baseline).
関連
- Links.md
- Meta.md
- ScrollRestoration.md
ScrollRestoration
Emulates browser scroll restoration on location changes and renders an inline <script> to prevent scroll flash. Should be rendered once, right before <Scripts>.
Props
| Name | Type | Description |
|---|---|---|
getKey | (location: Location, matches: RouteMatch[]) => string | Returns a key used to store/restore scroll positions. Defaults to location.key. Use location.pathname for pathname-based restoration. |
nonce | string | A nonce attribute on the inline <script> element for CSP compliance. |
storageKey | string | sessionStorage key for persisting scroll positions. Default: "react-router-scroll-positions". |
使用例
import { ScrollRestoration, Scripts } from "react-router";
export default function Root() {
return (
<html>
<body>
<ScrollRestoration
getKey={(location) => location.pathname}
/>
<Scripts />
</body>
</html>
);
}注意点
- Render only one
ScrollRestorationin the entire app. - Must be placed before
<Scripts>in the document. - Available in Framework and Data modes; not available in Declarative mode.
- Scroll positions are stored in
sessionStorageand survive page reloads within the same tab.
関連
- Scripts.md
- Links.md
.client Modules
Files suffixed with .client (e.g., utils.client.ts) are excluded from the server bundle. All their exports are undefined on the server.
Signature / Usage
// app/utils/browser.client.ts
export const supportsVibration = "vibrate" in window.navigator;
export const canUseDOM = typeof window !== "undefined";// app/routes/dashboard.tsx
import { useEffect } from "react";
import { supportsVibration } from "../utils/browser.client";
export default function Dashboard() {
useEffect(() => {
// Safe: useEffect only runs in the browser
if (supportsVibration) navigator.vibrate(100);
}, []);
return <div>Dashboard</div>;
}Naming Conventions
| Pattern | Example | Effect |
|---|---|---|
| File suffix | analytics.client.ts | Single file excluded from server |
| Directory name | .client/ or components.client/ | Entire directory excluded from server |
app/
├── .client/ # all files inside are client-only
│ ├── analytics.ts
│ └── feature-flags.ts
├── utils.client.ts # single client-only file
└── root.tsxNotes
- All exports from
.clientmodules resolve toundefinedduring SSR — never read them at module scope in shared code. - Safe usage locations:
useEffect, event handlers (click, submit, etc.). - Do not mark route modules as
.client— route files must be accessible to both server and client module graphs. - For more fine-grained control, use the `vite-env-only` plugin.
Related
- .server modules
- root.tsx
entry.client.tsx
Optional browser entry point in Framework Mode. Hydrates server-rendered markup on the client. React Router provides a default implementation when this file is absent.
Signature / Usage
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>
);
});Key APIs
| API | Source | Description |
|---|---|---|
HydratedRouter | react-router/dom | Sets up client-side routing and hydrates the router state sent from the server |
hydrateRoot | react-dom/client | React DOM API that attaches the React tree to the server-rendered HTML |
startTransition | react | Wraps hydration as a non-urgent update to avoid blocking the browser |
Notes
- This file is optional. If absent, React Router uses its built-in default.
- Create this file only when you need to initialize client-side libraries (analytics, error reporters, providers) before or around hydration.
- Use
npx react-router revealto scaffold the default implementation as a starting point. StrictModeis recommended for development but can be removed if it causes issues with third-party libraries.
Related
- entry.server.tsx
- root.tsx
entry.server.tsx
Optional server entry point in Framework Mode. Controls how the application generates HTTP responses on the server including streaming SSR. Required for non-Node runtimes (Cloudflare, Deno, etc.).
Signature / Usage
import { PassThrough } from "node:stream";
import type { EntryContext } from "react-router";
import { createReadableStreamFromReadable } from "@react-router/node";
import { ServerRouter } from "react-router";
import { renderToPipeableStream } from "react-dom/server";
export const streamTimeout = 5000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
): Promise<Response> {
return new Promise((resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<ServerRouter context={routerContext} url={request.url} />,
{
onShellReady() {
responseHeaders.set("Content-Type", "text/html");
const body = new PassThrough();
resolve(
new Response(createReadableStreamFromReadable(body), {
headers: responseHeaders,
status: responseStatusCode,
}),
);
pipe(body);
},
onShellError(error) { reject(error); },
},
);
setTimeout(abort, streamTimeout + 1000);
});
}Exports
Default export: handleRequest
| Parameter | Type | Description |
|---|---|---|
request | Request | Incoming HTTP request |
responseStatusCode | number | HTTP status code determined by React Router |
responseHeaders | Headers | Response headers to modify before sending |
routerContext | EntryContext | Router state, matched routes, and loader data |
Optional named exports
| Export | Signature | Description |
|---|---|---|
streamTimeout | number | Ms to wait for streamed promises before aborting. Default: React Router internal default |
handleDataRequest | (response, args) => Response | Modify responses for data-only requests (post-hydration loader/action calls) |
handleError | (error, args) => void | Custom server error logging. Suppresses built-in console logging when provided |
Notes
- This file is optional on Node — React Router provides a default Node implementation.
- For non-Node runtimes (Cloudflare Workers, Deno Deploy) this file is required.
- Set
streamTimeoutto control when React aborts deferred promises. ThesetTimeout(abort, streamTimeout + 1000)pattern gives rejected boundaries time to flush before the stream closes. handleErroris NOT called for intentionalthrow new Response(...)from loaders/actions — only for unexpected errors.- Avoid logging aborted requests in
handleError; checkrequest.signal.abortedfirst. - Use
npx react-router revealto scaffold the default implementation.
Related
- entry.client.tsx
- root.tsx
- react-router.config.ts
react-router.config.ts
Optional project configuration file for React Router Framework Mode. Located at the project root. Export a value satisfying Config from @react-router/dev/config.
Signature / Usage
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
appDirectory: "app",
buildDirectory: "build",
} satisfies Config;Options / Props
Core Options
| Name | Type | Default | Description |
|---|---|---|---|
ssr | boolean | true | Enable server-side rendering. Set to false for SPA mode (pre-renders to index.html) |
basename | string | "/" | URL prefix for the entire app |
appDirectory | string | "app" | Path to the app directory, relative to project root |
buildDirectory | string | "build" | Path to the build output directory |
serverModuleFormat | `"esm" \ | "cjs"` | "esm" |
serverBuildFile | string | "index.js" | Filename of the server build output (must end in .js) |
Prerendering
| Name | Type | Default | Description |
|---|---|---|---|
prerender | `string[] \ | (opts) => Promise<string[]>` | undefined |
export default {
prerender: async ({ getStaticPaths }) => {
const paths = await getStaticPaths();
return ["/", ...paths];
},
} satisfies Config;Server Bundles
| Name | Type | Default | Description |
|---|---|---|---|
serverBundles | ({ branch }) => string | undefined | Assign routes to separate server bundles by returning a bundle ID |
export default {
serverBundles: ({ branch }) =>
branch.some((r) => r.id === "admin") ? "admin" : "main",
} satisfies Config;Route Discovery
| Name | Type | Default | Description |
|---|---|---|---|
routeDiscovery | `{ mode: "lazy" \ | "initial", manifestPath?: string }` | { mode: "lazy", manifestPath: "/__manifest" } |
Future Flags
| Name | Type | Default | Description |
|---|---|---|---|
future | object | {} | Opt into upcoming features before they become defaults |
export default {
future: {
// future flags go here
},
} satisfies Config;Other Options
| Name | Type | Description |
|---|---|---|
allowedActionOrigins | string[] | Allowed origin hosts for action submissions (supports * / ** glob) |
buildEnd | async (opts) => void | Hook called after the full build completes |
presets | array | Plugin config presets for platform/tool integration |
Notes
- Setting
ssr: falseputs the app in SPA mode — no server is required, but loaders/actions still run at build time for prerendered pages. prerenderrequiresssr: trueor acts as the only output in SPA mode.serverBundlesenables splitting the server into multiple deployable units (useful for edge deployments).
Related
- routes.ts
- entry.server.tsx
conventions
| Name | Description | Path |
|---|---|---|
| .client Modules | Files suffixed with .client are excluded from the server bundle. All… | client-modules.md |
| entry.client.tsx | Optional browser entry point in Framework Mode. Hydrates server-rendered… | entry-client-tsx.md |
| entry.server.tsx | Optional server entry point in Framework Mode. Controls how the application… | entry-server-tsx.md |
| react-router.config.ts | Optional project configuration file for React Router Framework Mode.… | react-router-config-ts.md |
| root.tsx | The only required route in Framework Mode. Renders the root <html>… | root-tsx.md |
| routes.ts | Required configuration file that maps URL patterns to route module files.… | routes-ts.md |
| .server Modules | Files suffixed with .server are excluded from the client bundle entirely.… | server-modules.md |
root.tsx
The only required route in Framework Mode. Renders the root <html> document and acts as the parent to all other routes.
Signature / Usage
import { Outlet, Scripts, ScrollRestoration, Meta, Links } from "react-router";
// Optional: shared document shell (avoids duplication across App / HydrateFallback / ErrorBoundary)
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
// Required default export
export default function App() {
return <Outlet />;
}
// Optional error boundary shown when loader throws or render fails
export function ErrorBoundary() {
return <h1>Something went wrong</h1>;
}Required Rendered Components
| Component | Purpose |
|---|---|
<Outlet /> | Renders the active child route |
<Scripts /> | Injects all JS bundles. Accept nonce prop for CSP |
<ScrollRestoration /> | Restores scroll position on navigation. Accepts nonce |
<Meta /> | Renders meta exports from all routes (pre-React 19) |
<Links /> | Renders link exports from all routes (pre-React 19) |
Supported Route Module Exports
root.tsx supports all standard route module exports:
| Export | Description |
|---|---|
Layout | Shared document shell (optional, prevents re-mount) |
default | App component, renders <Outlet /> |
ErrorBoundary | Error UI when loader/render throws |
HydrateFallback | Loading UI shown before hydration completes |
loader | Server-side data loading |
action | Form submission handler |
meta | Root-level <meta> tags |
links | Root-level <link> elements |
headers | HTTP response headers |
Notes
- The
Layoutexport prevents FOUC (Flash of Unstyled Content) by keeping the document shell mounted acrossApp,HydrateFallback, andErrorBoundary. - Do not use
useLoaderData()insideLayout— if the loader threw an error it won't be available. UseuseRouteLoaderData("root")instead (returnsundefinedon error). - Keep
Layoutvery defensive: if it throws, the entire error boundary is bypassed and React Router falls back to a minimal built-in error UI. - Pass
nonce={nonce}to<Scripts />and<ScrollRestoration />when using nonce-based CSP.
Related
- routes.ts
- entry.client.tsx
- entry.server.tsx
routes.ts
Required configuration file that maps URL patterns to route module files. The default export must satisfy RouteConfig.
Signature / Usage
import {
type RouteConfig,
route,
index,
layout,
prefix,
} from "@react-router/dev/routes";
export default [
index("./home.tsx"),
route("about", "./about.tsx"),
layout("./auth/layout.tsx", [
route("login", "./auth/login.tsx"),
route("register", "./auth/register.tsx"),
]),
...prefix("concerts", [
index("./concerts/home.tsx"),
route(":city", "./concerts/city.tsx"),
]),
] satisfies RouteConfig;Route Helper Functions
| Helper | Signature | Description |
|---|---|---|
route | (pattern, moduleFile, children?) | Route with a URL pattern |
index | (moduleFile) | Index route rendered at the parent URL |
layout | (moduleFile, children) | Layout route (adds nesting without a URL segment) |
prefix | (pathPrefix, routes) | Adds a path prefix without introducing a parent route. Use with spread (...prefix(...)) |
relative | (directory) | Returns helpers that resolve module paths relative to directory |
Options / Props
route(pattern, moduleFile, children?)
| Param | Type | Description |
|---|---|---|
pattern | string | URL pattern. Supports :param, :param?, * (splat) |
moduleFile | string | Path to the route module file |
children | RouteConfig[] | Optional nested routes |
URL Pattern Syntax
| Pattern | Example | Matches |
|---|---|---|
| Static | "about" | /about |
| Dynamic segment | "teams/:teamId" | /teams/abc |
| Optional segment | ":lang?/categories" | /en/categories or /categories |
| Splat (catch-all) | "files/*" | /files/a/b/c |
File-System Routing Alternative
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;Notes
indexroutes cannot have children.prefixdoes not create a layout boundary — children are siblings that share a path prefix only.- Children render through
<Outlet />in the parent route module. - Splat params are accessed via
params["*"].
Related
- root.tsx
- react-router.config.ts
.server Modules
Files suffixed with .server (e.g., db.server.ts) are excluded from the client bundle entirely. The build will fail if .server code is accidentally imported by client code.
Signature / Usage
// app/utils/db.server.ts
import { PrismaClient } from "@prisma/client";
export const db = new PrismaClient({
datasources: { db: { url: process.env.DATABASE_URL } },
});// app/routes/dashboard.tsx (loader runs server-side only)
import { db } from "../utils/db.server";
export async function loader() {
return { items: await db.item.findMany() };
}Naming Conventions
| Pattern | Example | Effect |
|---|---|---|
| File suffix | auth.server.ts | Single file excluded from client bundle |
| Directory name | .server/ or utils.server/ | Entire directory excluded from client bundle |
app/
├── .server/ # all files inside are server-only
│ ├── auth.ts
│ ├── db.ts
│ └── email.ts
├── auth.server.ts # single server-only file
└── root.tsxNotes
- The build fails if a
.servermodule is transitively imported by any client code path — this acts as a safety net for secrets. - Suitable for: database clients, secret environment variables, server SDKs, email/SMS utilities.
- Do not mark route modules as
.server— route files must participate in both server and client module graphs. - For more fine-grained control, use the `vite-env-only` plugin.
Related
- .client modules
- entry.server.tsx
- react-router.config.ts
Backend For Frontend (BFF)
An architectural pattern where React Router acts as a dedicated web server that connects your frontend to backend services, rather than having the browser communicate directly with multiple APIs.
Availability: Framework Mode only.
詳細説明
The BFF strategy uses a web server scoped to:
- Serve the frontend web application
- Act as a gateway to databases, mailers, job queues, existing REST/GraphQL APIs, and third-party services
This is particularly valuable when you have mature backend applications (Ruby, Elixir, PHP, etc.) and don't want to migrate them to JavaScript. API tokens and secrets stay server-side and never reach the client bundle.
Full-Stack vs. BFF:
- Full-Stack: React Router connects directly to a database/services via server-side JavaScript
- BFF: React Router connects to existing backend APIs while serving the frontend
コード例
import escapeHtml from "escape-html";
export async function loader() {
const apiUrl = "https://api.example.com/some-data.json";
const res = await fetch(apiUrl, {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
});
const data = await res.json();
const prunedData = data.map((record) => {
return {
id: record.id,
title: record.title,
formattedBody: escapeHtml(record.content),
};
});
return { prunedData };
}注意点
- Use
fetch()in loaders and actions — React Router handles server-side data fetching - Only processed/pruned data is sent to the client (reduces kB transferred)
- Server code (like
escapeHtml) does not need to manage UI async states process.envsecrets are safe because they never enter the client bundle
関連
- State Management
- Progressive Enhancement
Automatic Code Splitting
React Router automatically splits code by route, reducing the JavaScript footprint for initial page loads.
Availability: Framework Mode only.
詳細説明
Route modules become separate bundler entry points. React Router uses URL segments to determine which bundles are needed in the browser. When a user visits /about, only the about.tsx bundle is loaded — contact.tsx is never fetched.
Server code removal: All server-only Route Module APIs (loader, action, headers) are automatically stripped from client bundles at build time. You can safely co-locate server-only code in route modules without it leaking to the browser.
コード例
// app/routes.ts
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
route("/contact", "./contact.tsx"),
route("/about", "./about.tsx"),
] satisfies RouteConfig;// about.tsx — after build, only Component remains in the client bundle
export async function loader() {
return { message: "hello" };
}
export async function action() {
console.log(Date.now());
return { ok: true };
}
export default function Component({ loaderData }) {
return <div>{loaderData.message}</div>;
}注意点
loader,action, andheadersexports are stripped from client bundles automatically — no manual tree-shaking required- Framework Mode handles bundle splitting transparently; no Vite/webpack config is needed
- Not available in Data or Declarative modes
関連
- Lazy Route Discovery
- Hot Module Replacement
- Type Safety
Network Concurrency Management
React Router automates network concurrency handling, mirroring browser behavior for navigations and extending it for concurrent fetcher requests.
Availability: Framework Mode and Data Mode. Not available in Declarative Mode.
詳細説明
Link Navigation and Form Submission
React Router mirrors the browser: clicking a new link (or submitting a new form) before the previous request completes cancels the in-flight request and immediately processes the new one.
Concurrent Fetchers (useFetcher)
Unlike navigations, multiple useFetcher instances can run simultaneously. React Router:
- Updates the UI immediately when any data arrives
- Cancels a fetcher's own in-flight request when a newer one is issued for the same fetcher
- During revalidation, cancels stale responses — only the freshest revalidation is committed
Timeline example (submission 2 resolves before submission 1):
submission 1: |----✓---------❌ (canceled, stale)
submission 2: |-----✓-----✅ (committed)
submission 3: |-----✓-----✅コード例
export async function loader({ request }) {
const { searchParams } = new URL(request.url);
const cities = await searchCities(searchParams.get("q"));
return cities;
}
export function CitySearchCombobox() {
const fetcher = useFetcher<typeof loader>();
return (
<fetcher.Form action="/city-search">
<Combobox aria-label="Cities">
<ComboboxInput
name="q"
onChange={(event) => fetcher.submit(event.target.form)}
/>
{fetcher.data ? (
<ComboboxList>
{fetcher.data.map((city) => (
<ComboboxOption key={city.id} value={city.name} />
))}
</ComboboxList>
) : null}
</Combobox>
</fetcher.Form>
);
}注意点
- Rare stale-data edge case: A canceled client request may still reach the server after a newer revalidation completes. Mitigate by sending timestamps with submissions and ignoring stale ones on the server
- The server still processes all requests even when the client cancels them — backend race conditions are out of scope
関連
- Race Conditions
- Form vs. Fetcher
Form vs. Fetcher
React Router provides two overlapping tools for form submissions. The primary criterion for choosing between them is whether the URL should change.
Availability: Framework Mode and Data Mode.
詳細説明
| Situation | Tool |
|---|---|
| Creating a record → redirect to new page | <Form> + useNavigation |
| Deleting a record → redirect to list | <Form> + useNavigation |
| Updating a single field in a list | useFetcher |
| Deleting an item while staying on the page | useFetcher |
| Loading data for a popover/combobox | useFetcher |
| Marking an article as read | useFetcher |
API mapping:
| Navigation/URL API | Fetcher API |
|---|---|
<Form> | <fetcher.Form> |
actionData (prop) | fetcher.data |
navigation.state | fetcher.state |
navigation.formAction | fetcher.formAction |
navigation.formData | fetcher.formData |
コード例
With URL change — creating a record:
export async function action({ request }: Route.ActionArgs) {
const recipe = await db.recipes.create(await request.formData());
return redirect(`/recipes/${recipe.id}`);
}
export function NewRecipe({ actionData }: Route.ComponentProps) {
const navigation = useNavigation();
const isSubmitting = navigation.formAction === "/recipes/new";
return (
<Form method="post">
<input name="title" />
<button type="submit">
{isSubmitting ? "Saving..." : "Create Recipe"}
</button>
</Form>
);
}Without URL change — deleting a list item:
function RecipeListItem({ recipe }: { recipe: Recipe }) {
const fetcher = useFetcher();
const isDeleting = fetcher.state !== "idle";
return (
<li>
<h2>{recipe.title}</h2>
<fetcher.Form method="post">
<input type="hidden" name="id" value={recipe.id} />
<button disabled={isDeleting} type="submit">
{isDeleting ? "Deleting..." : "Delete"}
</button>
</fetcher.Form>
</li>
);
}注意点
useFetchermaintains independent state per instance, making it ideal for per-item operations in a listuseFetchercan also callfetcher.load("/url")to fetch data without a form submission (e.g., for hover-triggered popovers)useNavigationreflects the state of the current page navigation; multiple simultaneous mutations requireuseFetcher
関連
- Concurrency
- Race Conditions
- State Management
- Progressive Enhancement
Hot Module Replacement (HMR)
HMR updates modules in your app without a full page reload, preserving browser state (form inputs, modal states) across code changes.
Availability: Framework Mode only (requires Vite).
詳細説明
React Router leverages React Fast Refresh for component updates. Vite handles the HMR transport layer. React Router's Vite plugin automatically ensures route module exports (loader, action, links, meta, headers) are HMR-compatible.
What state is preserved:
- Form field values
- Modal/dialog open states
- Scroll positions
- Any React component state that Fast Refresh can track
コード例
Named exports — compatible:
export const ComponentA = () => {}; // ✅
export function ComponentB() {} // ✅
export default function ComponentC() {} // ✅Anonymous exports — cause full reload:
export default () => {}; // ❌
export default function () {} // ❌Route module exports — automatically handled:
export const meta = { title: "Home" }; // ✅ HMR-compatible
export const links = [{ rel: "stylesheet" }]; // ✅
export async function loader() {} // ✅
export async function action() {} // ✅
export default function Route() {} // ✅
export const myValue = "custom export"; // ❌ causes full reloadMove custom values to a separate module:
// my-custom-value.ts
export const myValue = "some value";注意点
- Class components and HOCs that return classes do not preserve state with Fast Refresh
- Renaming hook destructure keys (e.g.,
{ pet }→{ dog }) causes a full reload for that component - Adding or removing hooks from a component triggers a full reload
- User-defined non-route exports in route files cause full reloads — move them to separate modules
- In some cases, explicit
keyprops are needed when modifying sibling elements
関連
- Code Splitting
- Lazy Route Discovery
Index Query Param (?index)
The ?index query parameter disambiguates form submissions between a parent route and its index route when both match the same URL path.
Availability: Framework Mode and Data Mode. Not available in Declarative Mode.
詳細説明
When multiple routes in a hierarchy match the same URL, navigations call all matching loaders, but form submissions call only one action. The ?index param explicitly targets the index route's action rather than the parent route's action.
Example route structure:
import { type RouteConfig, route, index } from "@react-router/dev/routes";
export default [
route("projects", "./pages/projects.tsx", [
index("./pages/projects/index.tsx"),
route(":id", "./pages/projects/project.tsx"),
]),
] satisfies RouteConfig;Both projects.tsx (parent) and projects/index.tsx (index) match /projects.
コード例
Explicit targeting:
<Form method="post" action="/projects" /> // → parent route action
<Form method="post" action="/projects?index" /> // → index route actionAutomatic handling — when <Form> has no action prop, React Router appends ?index automatically based on the current route context:
// Rendered inside projects/index.tsx
function ProjectsIndex() {
return <Form method="post" />; // auto-submits to /projects?index
}
// Rendered inside projects.tsx
function ProjectsLayout() {
return <Form method="post" />; // auto-submits to /projects
}With `useSubmit` and `useFetcher`:
const submit = useSubmit();
submit({}, { action: "/projects?index" });
const fetcher = useFetcher();
fetcher.submit({}, { action: "/projects?index" });
<fetcher.Form action="/projects?index" />;注意点
- In most cases
?indexis appended automatically — explicit use is only needed when you manually set anactionprop and want to target the index route - The param is essential in nested route architectures where the parent layout and index route share the exact same URL
関連
- Form vs. Fetcher
Lazy Route Discovery
Lazy Route Discovery loads route metadata progressively as users navigate, rather than sending the full route manifest on initial load. This is the default behavior in Framework Mode.
Availability: Framework Mode only.
詳細説明
On the initial SSR response, only routes required for that render are included in the manifest. As users navigate to new routes, React Router fetches the missing metadata from a /__manifest endpoint.
What the manifest contains: metadata about routes (JS/CSS imports, whether a loader/action exists) — not the actual module implementations. Modules are loaded separately on demand.
Route Discovery Flow: 1. User navigates to a route not in the current manifest 2. React Router requests /__manifest 3. Server responds with the required route metadata patch 4. React Router loads route modules and data 5. Navigation completes
Eager Discovery (anti-waterfall optimization): React Router automatically discovers all rendered <Link> and <NavLink> components via a batched manifest request — typically completing before the user clicks, making subsequent navigation feel synchronous.
コード例
// Links are discovered automatically by default
<Link to="/dashboard">Dashboard</Link>
// Opt out of discovery for a specific link
<Link to="/admin" discover="none">Admin</Link>Configuration in `react-router.config.ts`:
export default {
// Default: lazy discovery
routeDiscovery: {
mode: "lazy",
manifestPath: "/__manifest",
},
// Custom manifest path (multiple apps on same domain)
routeDiscovery: {
mode: "lazy",
manifestPath: "/my-app-manifest",
},
// Disable — include all routes in initial manifest
routeDiscovery: { mode: "initial" },
} satisfies Config;注意点
- Ensure
/__manifestrequests reach your React Router handler (not blocked by CDN or reverse proxy) - When caching the manifest endpoint via CDN, include the
versionandpathsquery parameters in the cache key - When running multiple React Router apps on the same domain, set a unique
manifestPathper app to avoid conflicts - Applications with many routes benefit most from this optimization
関連
- Code Splitting
- Hot Module Replacement
Progressive Enhancement
A web design strategy that ensures basic functionality works for everyone, while users with JavaScript and faster connections receive an enhanced experience.
Availability: Framework Mode only (requires SSR).
詳細説明
React Router supports progressive enhancement through Server-Side Rendering (SSR), which is the default in Framework Mode. The core principle: build the basic HTML-native version first, then layer on JavaScript enhancements.
Why it matters for performance:
Typical SPA:
HTML |---|
JavaScript |---------|
Data |---------------|
page rendered 👆
React Router SSR:
👇 first byte
HTML |---|-----------|
JavaScript |---------|
Data |---------------|
page rendered 👆While only 5% of users have permanently slow connections, 100% of users experience slow connections 5% of the time.
Core HTML primitives work without JavaScript:
<a href="...">→ rendered as a standard anchor, enhanced to client-side navigation when JS loads<Form method="post">→ submits to server, enhanced with client-side mutations when JS loads
Enhancement is iterative — you are not building two separate codebases. You build the basic version and add enhancements progressively.
コード例
Basic add-to-cart (works without JavaScript):
export function AddToCart({ id }) {
return (
<Form method="post" action="/add-to-cart">
<input type="hidden" name="id" value={id} />
<button type="submit">Add To Cart</button>
</Form>
);
}Enhanced with `useFetcher` (prevents navigation when JS loaded):
import { useFetcher } from "react-router";
export function AddToCart({ id }) {
const fetcher = useFetcher();
return (
<fetcher.Form method="post" action="/add-to-cart">
<input name="id" value={id} />
<button type="submit">
{fetcher.state === "submitting" ? "Adding..." : "Add To Cart"}
</button>
</fetcher.Form>
);
}Search box with navigation feedback:
import { useNavigation } from "react-router";
export function SearchBox() {
const navigation = useNavigation();
const isSearching = navigation.location?.pathname === "/search";
return (
<Form method="get" action="/search">
<input type="search" name="query" />
{isSearching ? <Spinner /> : <SearchIcon />}
</Form>
);
}注意点
- Everyone has JavaScript "disabled" until it finishes loading — design accordingly
- Use URLs (search params) as the source of truth for UI state instead of client-side state where possible
<Link>renders as a plain<a>tag, so it works without JavaScript- Progressive enhancement reduces the need for complex client-side state management
関連
- State Management
- Form vs. Fetcher
- Backend For Frontend
Race Conditions
React Router automatically handles the most common UI race conditions by mirroring browser network concurrency behavior.
Availability: Framework Mode and Data Mode. Not available in Declarative Mode.
詳細説明
Navigation and Forms
When a new navigation or form submission interrupts a pending one, React Router cancels the in-flight requests and immediately processes the new event — identical to how browsers handle document navigations.
Fetchers
Fetchers have nuanced behavior compared to navigations:
- Cannot interrupt other fetcher instances — each
useFetcher()call is independent - Can interrupt themselves — a new request on the same fetcher cancels its own previous request
- Interact during revalidation — after a fetcher action returns, React Router triggers revalidation for all page data; stale revalidation responses are canceled automatically
React Router commits only the "freshest" revalidation: any request that started earlier than one that has already returned is canceled.
コード例
Type-ahead combobox — no manual debouncing or cancellation needed:
export async function loader({ request }) {
const { searchParams } = new URL(request.url);
return searchCities(searchParams.get("q"));
}
export function CitySearchCombobox() {
const fetcher = useFetcher();
return (
<fetcher.Form action="/city-search">
<Combobox aria-label="Cities">
<ComboboxInput
name="q"
onChange={(event) => fetcher.submit(event.target.form)}
/>
{fetcher.data ? (
<ComboboxList>
{fetcher.data.map((city) => (
<ComboboxOption key={city.id} value={city.name} />
))}
</ComboboxList>
) : null}
</Combobox>
</fetcher.Form>
);
}Each onChange keystroke cancels the previous fetcher request automatically, so users never see results for a stale query.
注意点
- React Router only handles client-side race conditions. Your server still receives and may process all requests — backend data integrity is your responsibility
- This risk is considered low and mirrors the behavior of plain HTML
<form>elements in browsers - For high-stakes mutations, send timestamps with submissions and reject stale ones server-side
関連
- Concurrency
- Form vs. Fetcher
React Transitions
React Router v7 integrates with React 18's startTransition API and React 19's async transitions, allowing differentiation between urgent and non-urgent UI updates.
Status: Experimental — controlled via unstable_useTransitions prop. Subject to breaking changes in minor/patch releases.
詳細説明
Default Behavior (v7)
All router state updates are wrapped in React.startTransition. This can conflict with useSyncExternalStore and does not fully support React 19 async transitions.
Two Options
Opt-out (unstable_useTransitions={false}) — disables startTransition wrapping:
- Use when your app relies on
useSyncExternalStore - Use when synchronous updates are required
Opt-in (unstable_useTransitions={true}) — full React 19 async transition support:
- Requires React 19
<Link>and<Form>are wrapped automatically- Imperative calls (
navigate,submit,fetcher.load,fetcher.submit) must be manually wrapped instartTransition
State Surfacing with Opt-In
| Surfaced immediately (optimistic) | Deferred until complete |
|---|---|
useNavigation() | useLocation() |
useRevalidator() | useMatches() |
useActionData() | useLoaderData() |
useFetcher() / useFetchers() | useRouteError() |
コード例
Applying the prop:
// Framework Mode (entry.client.tsx)
<HydratedRouter unstable_useTransitions />
// Data Mode
<RouterProvider unstable_useTransitions />
// Declarative Mode
<BrowserRouter unstable_useTransitions />Imperative navigation with transitions (opt-in mode):
// ✅ Correct — promise is returned
startTransition(() => navigate("/path"));
// ✅ Correct — promise is awaited
startTransition(async () => {
setOptimistic(something);
await navigate("/path");
});
// ❌ Wrong — promise is neither returned nor awaited
startTransition(() => {
setOptimistic(something);
navigate("/path");
});注意点
- The opt-in behavior (
unstable_useTransitions=true) is planned to become the default in React Router v8 - Known bug:
popstatenavigations (browser back/forward) have an issue with optimistic states if the target route suspends. Defer optimistic updates with a timer or microtask as a workaround - When using opt-in mode, always return or await the navigation promise inside
startTransition— a floating promise will not be tracked correctly
関連
- State Management
- Concurrency
Explanation
| Name | Description | Path |
|---|---|---|
| Automatic Code Splitting | React Router automatically splits code by route, reducing the JavaScript footprint for initial page loads. | code-splitting.md |
| Backend For Frontend (BFF) | An architectural pattern where React Router acts as a dedicated web server that connects your frontend to backend services, rather than having the browser communicate directly with multiple APIs. | backend-for-frontend.md |
| Form vs. Fetcher | React Router provides two overlapping tools for form submissions. The primary criterion for choosing between them is whether the URL should change. | form-vs-fetcher.md |
| Hot Module Replacement (HMR) | HMR updates modules in your app without a full page reload, preserving browser state (form inputs, modal states) across code changes. | hot-module-replacement.md |
Index Query Param (?index) | The ?index query parameter disambiguates form submissions between a parent route and its index route when both match the same URL path. | index-query-param.md |
| Lazy Route Discovery | Lazy Route Discovery loads route metadata progressively as users navigate, rather than sending the full route manifest on initial load. | lazy-route-discovery.md |
| Network Concurrency Management | React Router automates network concurrency handling, mirroring browser behavior for navigations and extending it for concurrent fetcher requests. | concurrency.md |
| Progressive Enhancement | A web design strategy that ensures basic functionality works for everyone, while users with JavaScript and faster connections receive an enhanced experience. | progressive-enhancement.md |
| Race Conditions | React Router automatically handles the most common UI race conditions by mirroring browser network concurrency behavior. | race-conditions.md |
| React Transitions | React Router v7 integrates with React 18's startTransition API and React 19's async transitions, allowing differentiation between urgent and non-urgent UI updates. | react-transitions.md |
| State Management | React Router replaces most client-side state management by synchronizing server and client state through loaders, actions, and automatic revalidation. | state-management.md |
| Type Safety | React Router generates TypeScript types per route, providing type-safe loader args, action args, and component props automatically. | type-safety.md |
State Management
React Router replaces most client-side state management by synchronizing server and client state through loaders, actions, and automatic revalidation.
Availability: Framework Mode primarily; Data Mode for some patterns.
詳細説明
Where State Lives in React Router
Instead of managing client-side caches of server data, React Router provides natural state homes:
| State type | Where to store |
|---|---|
| Server data (lists, records) | Loaders + automatic revalidation |
| Pending/submitting state | useNavigation / useFetcher |
| Persistent UI preferences | Cookies |
| Shareable UI state (filters, tabs) | URL search params |
| Server-managed sessions | Server sessions |
| Action results / validation errors | actionData prop |
Anti-Pattern to Avoid
Manual state synchronization with useState + useEffect + fetch:
// ❌ Complex, error-prone
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState(null);
// ... manual fetch, error handling, state syncReact Router Approach
Let the framework handle network state:
// ✅ Simple
const navigation = useNavigation();
const isSubmitting = navigation.formAction === "/signup";
const errors = actionData?.errors;コード例
URL search params for UI state (shareable, no useState needed):
import { Form, useSearchParams } from "react-router";
export function List() {
const [searchParams] = useSearchParams();
const view = searchParams.get("view") || "list";
return (
<div>
<Form>
<button name="view" value="list">View as List</button>
<button name="view" value="details">View with Details</button>
</Form>
{view === "list" ? <ListView /> : <DetailView />}
</div>
);
}Cookies for persistent UI state (e.g., sidebar open/closed):
import { createCookie, data } from "react-router";
export const prefs = createCookie("prefs");
export async function loader({ request }: Route.LoaderArgs) {
const cookie = (await prefs.parse(request.headers.get("Cookie"))) || {};
return data({ sidebarIsOpen: cookie.sidebarIsOpen });
}
export async function action({ request }: Route.ActionArgs) {
const cookie = (await prefs.parse(request.headers.get("Cookie"))) || {};
const formData = await request.formData();
cookie.sidebarIsOpen = formData.get("sidebar") === "open";
return data(true, {
headers: { "Set-Cookie": await prefs.serialize(cookie) },
});
}注意点
- React Router handles most state needs; reach for Redux/React Query/Apollo only for complex client-only state or advanced caching scenarios
- Automatic revalidation after actions keeps data fresh without manual cache invalidation
- URL-based state is inherently shareable and survives page refreshes
関連
- Form vs. Fetcher
- Progressive Enhancement
- React Transitions
Type Safety
React Router generates TypeScript types per route, providing type-safe loader args, action args, and component props automatically.
Availability: Framework Mode only.
詳細説明
React Router executes your route config (app/routes.ts) and generates +types/<route>.d.ts files in .react-router/types/. TypeScript's rootDirs configuration makes these importable as if they were next to your route modules.
Generated types per route:
LoaderArgsClientLoaderArgsActionArgsClientActionArgsHydrateFallbackPropsComponentProps(for the default export)ErrorBoundaryProps
Types are generated automatically during react-router dev and vite.createServer. For CI pipelines, run react-router typegen manually before tsc.
コード例
Route config:
// app/routes.ts
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route("products/:id", "./routes/product.tsx"),
] satisfies RouteConfig;Route module with generated types:
// app/routes/product.tsx
import type { Route } from "./+types/product";
export function loader({ params }: Route.LoaderArgs) {
// params is typed as { id: string }
return { planet: `world #${params.id}` };
}
export default function Component({
loaderData, // typed as { planet: string }
}: Route.ComponentProps) {
return <h1>Hello, {loaderData.planet}!</h1>;
}CLI commands:
# Generate types once (for CI)
react-router typegen
# Watch mode
react-router typegen --watch注意点
- Types are generated into
.react-router/types/— commit this directory or generate it in CI before runningtsc - When running
react-router dev, types are regenerated on every route config change automatically satisfies RouteConfigon your route config is required for type inference to work correctly- Each route gets its own isolated type file, so
Route.LoaderArgsin one route is unrelated to another route'sRoute.LoaderArgs
関連
- Code Splitting
- Lazy Route Discovery
hooks
| Name | Description | Path |
|---|---|---|
| useActionData | Returns the data from the route's action function after a form submission. | useActionData.md |
| useAsyncError | Returns the rejection value from the nearest <Await> component. | useAsyncError.md |
| useAsyncValue | Returns the resolved value from the nearest <Await> ancestor component. | useAsyncValue.md |
| useBeforeUnload | Registers a callback on the browser's beforeunload event, allowing cleanup or… | useBeforeUnload.md |
| useBlocker | Blocks in-app SPA navigations and returns a Blocker object that lets you… | useBlocker.md |
| useFetcher | Creates a fetcher for loading data or submitting forms without causing a… | useFetcher.md |
| useFetchers | Returns an array of all in-flight Fetcher objects. | useFetchers.md |
| useFormAction | Resolves the URL of the closest route in the component hierarchy and… | useFormAction.md |
| useHref | Resolves a path against the current location and returns the resulting… | useHref.md |
| useLocation | Returns the current Location object. | useLocation.md |
| useLoaderData | Returns data from the closest route's loader or clientLoader function. | useLoaderData.md |
| useMatch | Returns a PathMatch object if the given pattern matches the current URL,… | useMatch.md |
| useMatches | Returns an array of UIMatch objects representing every active route in… | useMatches.md |
| useNavigate | Returns a function for programmatic navigation in response to user… | useNavigate.md |
| useNavigation | Returns the current Navigation object representing any in-progress… | useNavigation.md |
| useOutletContext | Returns the context value passed via the context prop on the parent route's… | useOutletContext.md |
| useParams | Returns an object of dynamic route parameters extracted from the current… | useParams.md |
| useRevalidator | Returns a revalidate function and the current revalidation state, allowing… | useRevalidator.md |
| useRouteError | Returns the error thrown during a route's loader, action, or component render. | useRouteError.md |
| useRouteLoaderData | Returns the loader data for any route in the hierarchy by its route ID. | useRouteLoaderData.md |
| useSearchParams | Returns a tuple of the current URL's URLSearchParams and a setter function. | useSearchParams.md |
| useSubmit | Returns a SubmitFunction for submitting forms programmatically — the… | useSubmit.md |
| useViewTransitionState | Returns true when there is an active View Transition targeting the… | useViewTransitionState.md |
useActionData
Returns the data from the route's action function after a form submission. Returns undefined if no action has been called yet.
Signature
function useActionData<T = any>(): SerializeFrom<T> | undefinedUsage
import { Form, useActionData } from "react-router";
export async function action({ request }) {
const body = await request.formData();
const name = body.get("visitorsName");
return { message: `Hello, ${name}` };
}
export default function Invoices() {
const data = useActionData<typeof action>();
return (
<Form method="post">
<input type="text" name="visitorsName" />
{data ? data.message : "Waiting..."}
</Form>
);
}Notes
- Returns
undefineduntil a form submission triggers the action - Only captures data from
POSTnavigation form submissions - Not available in Declarative mode
Related
- useLoaderData — access loader return values
- useNavigation — track submission state
- useSubmit — submit forms programmatically
useAsyncError
Returns the rejection value from the nearest <Await> component. Used inside the errorElement of an <Await> to render error UI.
Signature
function useAsyncError(): unknownUsage
import { Await, useAsyncError } from "react-router";
function AsyncErrorUI() {
const error = useAsyncError();
return <p>Something went wrong: {String(error)}</p>;
}
<Await
resolve={promiseThatMightReject}
errorElement={<AsyncErrorUI />}
/>Notes
- Must be used inside the
errorElementprop of an<Await>component - Returns
unknown— narrow the type before accessing properties - Not available in Declarative mode
Related
- useAsyncValue — resolved value from
<Await> - useRouteError — error from route loaders/actions
useAsyncValue
Returns the resolved value from the nearest <Await> ancestor component. Used inside components rendered as children of <Await>.
Signature
function useAsyncValue(): unknownUsage
import { Await, useAsyncValue } from "react-router";
function ResolvedData() {
const value = useAsyncValue();
return <pre>{JSON.stringify(value, null, 2)}</pre>;
}
// In your route component:
<Await resolve={somePromise}>
<ResolvedData />
</Await>Notes
- Must be called inside a component that is a descendant of
<Await> - Returns
unknown— narrow the type before use - The
<Await>component must be wrapped in a<Suspense>boundary to handle the loading state - Not available in Declarative mode
Related
- useAsyncError — access rejection errors from
<Await> - useLoaderData — access route loader data
useBeforeUnload
Registers a callback on the browser's beforeunload event, allowing cleanup or confirmation prompts when the user leaves the page entirely (hard navigation, tab close, etc.).
Signature
function useBeforeUnload(
callback: (event: BeforeUnloadEvent) => any,
options?: { capture?: boolean },
): voidOptions
| Option | Type | Default | Description |
|---|---|---|---|
capture | boolean | false | Attach listener in the capture phase instead of the bubbling phase |
Usage
import { useBeforeUnload } from "react-router";
function Editor() {
const [dirty, setDirty] = useState(false);
useBeforeUnload((event) => {
if (dirty) {
event.preventDefault();
event.returnValue = ""; // Required by some browsers
}
});
return <textarea onChange={() => setDirty(true)} />;
}Notes
- Modern browsers do not display custom messages — calling
event.preventDefault()shows a generic browser dialog - This hook covers full-page unloads only; use
useBlockerto intercept in-app SPA navigations - Available in all modes: Framework, Data, and Declarative
Related
- useBlocker — block in-app SPA navigations with a confirmation UI
useBlocker
Blocks in-app SPA navigations and returns a Blocker object that lets you present a confirmation UI before proceeding.
Signature
function useBlocker(shouldBlock: boolean | BlockerFunction): BlockerBlockerFunction receives { currentLocation, nextLocation, historyAction } and returns a boolean.
Usage
import { useCallback, useState } from "react";
import { type BlockerFunction, useBlocker } from "react-router";
function UnsavedForm() {
const [value, setValue] = useState("");
const shouldBlock = useCallback<BlockerFunction>(
({ currentLocation, nextLocation }) =>
value !== "" && currentLocation.pathname !== nextLocation.pathname,
[value],
);
const blocker = useBlocker(shouldBlock);
return (
<form>
<input value={value} onChange={e => setValue(e.target.value)} />
{blocker.state === "blocked" && (
<div>
<p>Leave without saving?</p>
<button type="button" onClick={() => blocker.proceed()}>Leave</button>
<button type="button" onClick={() => blocker.reset()}>Stay</button>
</div>
)}
</form>
);
}Blocker object
| Property | Type | Description |
|---|---|---|
state | `"unblocked" \ | "blocked" \ |
location | `Location \ | undefined` |
proceed() | () => void | Allow the blocked navigation |
reset() | () => void | Cancel the block; stay on current page |
Notes
- Does not block hard reloads, browser back/forward, or cross-origin navigations
- Prefer the function form of
shouldBlockfor granular control (e.g. ignore query-only changes) - Not available in Declarative mode
Related
- useBeforeUnload — hook into browser's
beforeunloadevent - useNavigate — programmatic navigation
useFetcher
Creates a fetcher for loading data or submitting forms without causing a navigation. Each fetcher tracks its own independent state.
Signature
function useFetcher<T = any>(options?: {
key?: string;
}): FetcherWithComponents<SerializeFrom<T>>Usage
import { useFetcher } from "react-router";
function AddToCart({ productId }) {
const fetcher = useFetcher();
return (
<fetcher.Form method="post" action="/cart">
<input type="hidden" name="productId" value={productId} />
<button type="submit">
{fetcher.state !== "idle" ? "Adding..." : "Add to Cart"}
</button>
</fetcher.Form>
);
}Fetcher object
| Property / Method | Type | Description |
|---|---|---|
state | `"idle" \ | "loading" \ |
data | `SerializeFrom<T> \ | undefined` |
Form | Component | Form component bound to this fetcher |
load(href) | function | Load data from a route without navigating |
submit(target, options?) | function | Submit data to a route without navigating |
reset() | function | Reset the fetcher to its idle state |
Options
| Option | Type | Description |
|---|---|---|
key | string | Shared key to access the same fetcher from multiple components |
Notes
- Fetchers do not cause navigation — ideal for likes, inline edits, background refreshes
- Use
keyto share fetcher state across components:useFetcher({ key: "my-key" }) - Not available in Declarative mode
Related
- useFetchers — inspect all in-flight fetchers
- useNavigation — track navigation-level submission state
- useSubmit — programmatic navigation-based submission
useFetchers
Returns an array of all in-flight Fetcher objects. Useful for components that did not create fetchers themselves but want to participate in optimistic UI.
Signature
function useFetchers(): (Fetcher & { key: string })[]Usage
import { useFetchers } from "react-router";
function GlobalPendingIndicator() {
const fetchers = useFetchers();
const isLoading = fetchers.some(f => f.state !== "idle");
return isLoading ? <Spinner /> : null;
}Notes
- Returns only in-flight (active) fetchers, not idle/completed ones
- Each entry includes a
keystring identifying the fetcher - Commonly used to build optimistic UI in components that don't own the fetchers
- Not available in Declarative mode
Related
- useFetcher — create and manage individual fetchers
useFormAction
Resolves the URL of the closest route in the component hierarchy and optionally appends a sub-action path. Used internally by <Form> but available for custom use.
Signature
function useFormAction(
action?: string,
options?: { relative?: RelativeRoutingType },
): stringOptions
| Option | Type | Default | Description |
|---|---|---|---|
action | string | undefined | Sub-path to append to the closest route URL |
relative | `"route" \ | "path"` | "route" |
Usage
import { useFormAction } from "react-router";
function DeleteButton() {
const destroyAction = useFormAction("destroy");
// e.g. "/invoices/123/destroy"
return <button formAction={destroyAction}>Delete</button>;
}Notes
- Returns the closest route's URL, not the current browser URL
- Not available in Declarative mode
Related
- useHref — resolve any path to an href string
- useLocation — current location object
useHref
Resolves a path against the current location and returns the resulting href string.
Signature
function useHref(
to: To,
options?: { relative?: RelativeRoutingType },
): stringOptions
| Option | Type | Default | Description |
|---|---|---|---|
relative | `"route" \ | "path"` | "route" |
Usage
import { useHref } from "react-router";
function SomeComponent() {
const href = useHref("some/where");
// e.g. "/resolved/some/where"
return <a href={href}>Link</a>;
}Notes
- Useful when you need a resolved URL string to pass to non-React-Router elements (e.g. native
<a>) - Available in all modes: Framework, Data, and Declarative
Related
- useLocation — current location object
- useNavigate — programmatic navigation
useLoaderData
Returns data from the closest route's loader or clientLoader function. Available in Framework and Data modes only.
Signature
function useLoaderData<T = any>(): SerializeFrom<T>Usage
import { useLoaderData } from "react-router";
export async function loader() {
return await fakeDb.invoices.findAll();
}
export default function Invoices() {
let invoices = useLoaderData<typeof loader>();
return <ul>{invoices.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}Notes
- Use
typeof loaderas the generic to get proper TypeScript typing - Data is serialized via
SerializeFrom<T>— functions and non-serializable values are stripped - Must be used in a component rendered by a route that has an associated loader
- Not available in Declarative mode
Related
- useRouteLoaderData — access loader data from any route by ID
- useActionData — access action return values
useLocation
Returns the current Location object. Triggers a re-render whenever the location changes.
Signature
function useLocation(): LocationUsage
import { useEffect } from "react";
import { useLocation } from "react-router";
function Analytics() {
const location = useLocation();
useEffect(() => {
ga("send", "pageview");
}, [location]);
return null;
}Location object properties
| Property | Type | Description |
|---|---|---|
pathname | string | The current URL path |
search | string | The query string (e.g. "?tab=1") |
hash | string | The URL hash (e.g. "#section") |
state | unknown | State passed via navigate or <Link state> |
key | string | Unique key for this location entry |
Notes
- Commonly used with
useEffectfor analytics, scroll restoration, or animations on route change - Available in all modes: Framework, Data, and Declarative
Related
- useNavigate — programmatic navigation
- useParams — dynamic route parameters
- useSearchParams — read and update query string params
useMatch
Returns a PathMatch object if the given pattern matches the current URL, or null if it does not. Useful for determining "active" state of navigation elements.
Signature
function useMatch<
ParamKey extends ParamParseKey<Path>,
Path extends string,
>(
pattern: PathPattern<Path> | Path,
): PathMatch<ParamKey> | nullUsage
import { useMatch } from "react-router";
function NavItem({ to, children }) {
const match = useMatch(to);
return (
<a href={to} style={{ fontWeight: match ? "bold" : "normal" }}>
{children}
</a>
);
}PathMatch object
| Property | Type | Description |
|---|---|---|
pathname | string | The matched portion of the URL |
pathnameBase | string | The base of the matched pathname |
pattern | PathPattern | The pattern that was matched |
params | Params<ParamKey> | Extracted dynamic params |
Notes
- Returns
nullwhen the pattern does not match — guard before accessing properties - Available in all modes: Framework, Data, and Declarative
Related
- useMatches — all active route matches in the hierarchy
- useParams — current route's dynamic params
- useLocation — current location object
useMatches
Returns an array of UIMatch objects representing every active route in the current match hierarchy, from root to the current route.
Signature
function useMatches(): UIMatch[]Usage
import { useMatches } from "react-router";
function Breadcrumbs() {
const matches = useMatches();
return (
<nav>
{matches
.filter(m => m.handle?.breadcrumb)
.map(m => <span key={m.id}>{m.handle.breadcrumb}</span>)}
</nav>
);
}UIMatch object
| Property | Type | Description |
|---|---|---|
id | string | Route ID |
pathname | string | Matched pathname |
params | Params | Dynamic route params |
data | unknown | Loader data for this route |
handle | unknown | The route module's handle export |
Notes
- Commonly used for breadcrumbs, layout decisions, and accessing parent loader data
- The
handleexport on a route module is an arbitrary value — use it to attach metadata - Not available in Declarative mode
Related
- useLoaderData — current route's loader data
- useRouteLoaderData — loader data by route ID
- useMatch — test a single pattern against the current URL
useNavigate
Returns a function for programmatic navigation in response to user interactions or effects.
Signature
function useNavigate(): NavigateFunction
// The returned function:
navigate(to: To, options?: NavigateOptions): void | Promise<void>
navigate(delta: number): void | Promise<void>Usage
import { useNavigate } from "react-router";
function SomeComponent() {
const navigate = useNavigate();
return (
<button onClick={() => navigate("/dashboard")}>Go to Dashboard</button>
);
}Options
| Option | Type | Description |
|---|---|---|
replace | boolean | Replace current history entry instead of pushing |
state | any | Attach state to the location object |
relative | `"route" \ | "path"` |
preventScrollReset | boolean | Do not scroll to top after navigation (Framework/Data only) |
flushSync | boolean | Wrap DOM updates in ReactDOM.flushSync (Framework/Data only) |
viewTransition | boolean | Enable document.startViewTransition (Framework/Data only) |
Notes
- Prefer
redirect()inside loaders/actions overuseNavigatewhere possible navigate(-1)navigates back — only use when you are certain a history entry exists- In Declarative mode the function returns
void; in Framework/Data modes it returnsPromise<void> - Available in all modes: Framework, Data, and Declarative
Related
- useLocation — read current location
- useBlocker — block navigations conditionally
useNavigation
Returns the current Navigation object representing any in-progress navigation, defaulting to an idle state when none is active.
Signature
function useNavigation(): NavigationUsage
import { useNavigation } from "react-router";
function GlobalSpinner() {
const navigation = useNavigation();
return (
<div>
{navigation.state !== "idle" && <Spinner />}
</div>
);
}Navigation object properties
| Property | Type | Description |
|---|---|---|
state | `"idle" \ | "loading" \ |
formData | `FormData \ | undefined` |
location | `Location \ | undefined` |
formMethod | `string \ | undefined` |
formAction | `string \ | undefined` |
Notes
- Use
navigation.state === "submitting"or"loading"to render pending UI navigation.formDatais set during form submission navigations — useful for optimistic UI- Not available in Declarative mode
Related
- useNavigate — programmatic navigation
- useFetcher — for non-navigation submissions
- useRevalidator — manual revalidation state
useOutletContext
Returns the context value passed via the context prop on the parent route's <Outlet>. Enables parent-to-child route state sharing without a separate context provider.
Signature
function useOutletContext<Context = unknown>(): ContextUsage
Parent route:
import { Outlet } from "react-router";
function Dashboard() {
const [user, setUser] = useState(null);
return <Outlet context={{ user, setUser }} />;
}Child route:
import { useOutletContext } from "react-router";
function DashboardMessages() {
const { user } = useOutletContext<{ user: User }>();
return <p>Hello, {user.name}</p>;
}Best practice — typed custom hook
Export a typed hook from the parent module to give consumers type safety:
// dashboard.tsx
type ContextType = { user: User | null };
export function useDashboard() {
return useOutletContext<ContextType>();
}
// dashboard/messages.tsx
import { useDashboard } from "../dashboard";
const { user } = useDashboard();Notes
- Available in all modes: Framework, Data, and Declarative
Related
- useMatches — access route handle data across the hierarchy
- useLoaderData — loader-provided data for the current route
useParams
Returns an object of dynamic route parameters extracted from the current URL, keyed by their names as defined in the route pattern.
Signature
function useParams<
ParamsOrKey extends string | Record<string, string | undefined> = string,
>(): Readonly<
[ParamsOrKey] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey>
>Usage
import { useParams } from "react-router";
// Route: /posts/:postId/comments/:commentId
export default function Comment() {
const { postId, commentId } = useParams();
return <h1>Post {postId}, Comment {commentId}</h1>;
}Notes
- Child routes inherit all params from their parent routes
- Catchall segments are available via the
"*"key:const { "*": rest } = useParams() - Returns a readonly object — do not mutate directly
- Available in all modes: Framework, Data, and Declarative
Related
- useMatch — match a specific pattern against the current URL
- useLocation — access the full current location object
useRevalidator
Returns a revalidate function and the current revalidation state, allowing you to manually re-run all active loaders outside of normal mutation flows.
Signature
function useRevalidator(): {
revalidate: () => Promise<void>;
state: "idle" | "loading";
}Usage
import { useRevalidator } from "react-router";
function WindowFocusRevalidator() {
const { revalidate, state } = useRevalidator();
useWindowFocus(() => {
revalidate();
});
return <div hidden={state === "idle"}>Refreshing...</div>;
}Notes
- Page data is automatically revalidated after actions — use this hook only for special cases such as polling or window focus events
- For user-triggered mutations prefer
<Form>,useSubmit, oruseFetcherinstead - Not available in Declarative mode
Related
- useFetcher — submit/load without navigation
- useNavigation — navigation-level loading state
useRouteError
Returns the error thrown during a route's loader, action, or component render. Must be called inside a route module's ErrorBoundary component.
Signature
function useRouteError(): unknownUsage
import { useRouteError } from "react-router";
export function ErrorBoundary() {
const error = useRouteError();
if (error instanceof Error) {
return <div>Error: {error.message}</div>;
}
return <div>Unknown error occurred</div>;
}Notes
- Return type is
unknown— always check/narrow the type before accessing properties - Catches errors from loaders, actions, and rendering errors within the route boundary
- Not available in Declarative mode
Related
- useAsyncError — errors from
<Await>rejected promises - useAsyncValue — resolved values from
<Await>
useRouteLoaderData
Returns the loader data for any route in the hierarchy by its route ID. Useful for accessing data from parent or sibling routes.
Signature
function useRouteLoaderData<T = any>(
routeId: string,
): SerializeFrom<T> | undefinedUsage
import { useRouteLoaderData } from "react-router";
function SomeComponent() {
const { user } = useRouteLoaderData("root");
}Route ID mapping (file-based routing)
| Route file | Route ID |
|---|---|
app/root.tsx | "root" |
app/routes/teams.tsx | "routes/teams" |
app/routes/teams.$id.tsx | "routes/teams.$id" |
Custom IDs can be assigned in routes.ts:
route("/", "containers/app.tsx", { id: "app" })
// then:
useRouteLoaderData("app")Notes
- Returns
undefinedif the route ID is not found or has not loaded yet - Not available in Declarative mode
Related
- useLoaderData — access loader data for the current route only
- useMatches — access all matched routes and their data
useSearchParams
Returns a tuple of the current URL's URLSearchParams and a setter function. Setting params causes a navigation.
Signature
function useSearchParams(
defaultInit?: URLSearchParamsInit,
): [URLSearchParams, SetURLSearchParams]Usage
import { useSearchParams } from "react-router";
function Tabs() {
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get("tab") ?? "overview";
return (
<button onClick={() => setSearchParams({ tab: "settings" })}>
Settings
</button>
);
}Setter formats
setSearchParams("?tab=1"); // string
setSearchParams({ tab: "1" }); // object
setSearchParams({ brand: ["nike", "reebok"] }); // array values
setSearchParams([["tab", "1"]]); // array of tuples
setSearchParams(new URLSearchParams("?tab=1")); // URLSearchParams
setSearchParams(prev => { prev.set("tab", "2"); return prev; }); // callbackNotes
- The
searchParamsobject is a stable reference — safe to use inuseEffectdependency arrays - The callback form of
setSearchParamsdoes not support React's queuing likesetStatedoes; multiple calls in the same tick do not accumulate defaultInitprovides a fallback value but does not update the URL on first render- Available in all modes: Framework, Data, and Declarative
Related
- useLocation — full location object including
search - useNavigate — programmatic navigation
useSubmit
Returns a SubmitFunction for submitting forms programmatically — the imperative counterpart to the <Form> component.
Signature
function useSubmit(): SubmitFunctionUsage
import { useSubmit } from "react-router";
function AutoSaveForm() {
const submit = useSubmit();
return (
<Form onChange={(event) => submit(event.currentTarget)}>
<input name="title" />
</Form>
);
}Notes
- Use when you need to trigger a submission based on conditions, timers, or events rather than a submit button click
- The submitted form data causes a navigation (unlike
useFetcher().submit) - Not available in Declarative mode
Related
- useFetcher — submit without causing a navigation
- useNavigation — track the resulting navigation state
- useActionData — access the action's return value
useViewTransitionState
Returns true when there is an active View Transition targeting the specified location, enabling fine-grained CSS view-transition-name assignments during transitions.
Signature
function useViewTransitionState(
to: To,
options?: { relative?: RelativeRoutingType },
): booleanOptions
| Option | Type | Default | Description |
|---|---|---|---|
relative | `"route" \ | "path"` | "route" |
Usage
import { Link, useViewTransitionState } from "react-router";
function NavItem({ to, children }) {
const isTransitioning = useViewTransitionState(to);
return (
<Link
to={to}
viewTransition
style={{
viewTransitionName: isTransitioning ? "active-item" : undefined,
}}
>
{children}
</Link>
);
}Notes
- View transitions must first be enabled for the navigation via
<Link viewTransition>,<Form viewTransition>,submit(..., { viewTransition: true }), ornavigate(..., { viewTransition: true }) - Not available in Declarative mode
Related
- useNavigate — navigate with
viewTransitionoption
Accessibility
React Router renders standard HTML elements by default, providing built-in browser accessibility behaviors. For client-side routing, additional focus management and screen-reader announcements are needed.
実装方法
<Link>renders an<a>tag — browser accessibility built in<NavLink>addsaria-currentcontext for assistive technology on the active route- When using
<Scripts>, manage focus on route change and add live-region announcements for screen readers - Follow WCAG guidelines and use semantic HTML throughout the app
コード例
// Navigation menu using NavLink for screen-reader context
import { NavLink } from "react-router";
export function Nav() {
return (
<nav>
<NavLink to="/">Home</NavLink>
<NavLink to="/about">About</NavLink>
</nav>
);
}注意点
- Without focus management, screen readers won't announce route changes
- Apps should work even with JavaScript disabled (progressive enhancement)
- Refer to Marcy Sutton's 2019 research on accessible client-side routing
- Available in Framework, Data, and Declarative modes
関連
- Link
- NavLink
Client Data
Use clientLoader and clientAction to fetch and mutate data directly in the browser. The primary mechanism for data handling in SPA mode; also useful in SSR to skip the server hop or combine server + client data.
Available in: Framework mode only.
実装方法
1. Export clientLoader from a route module to load data in the browser 2. Set clientLoader.hydrate = true to run the loader during initial hydration 3. Export HydrateFallback to show UI while the client loader runs on first load 4. Call serverLoader() inside clientLoader to combine server and client data
コード例
// Fullstack state: merge server DB data with client IndexedDB data
export async function loader({ request }: Route.LoaderArgs) {
return getPartialDataFromDb({ request });
}
export async function clientLoader({
request,
serverLoader,
}: Route.ClientLoaderArgs) {
const [serverData, clientData] = await Promise.all([
serverLoader(),
getClientData(request),
]);
return { ...serverData, ...clientData };
}
clientLoader.hydrate = true as const;
export function HydrateFallback() {
return <p>Loading...</p>;
}
export default function Component({ loaderData }: Route.ComponentProps) {
return <div>{loaderData.title}</div>;
}注意点
clientLoaderis NOT called on hydration unlessclientLoader.hydrate = trueis set- When using
hydrate = truewithoutHydrateFallback, ensureloaderandclientLoaderreturn identical data to avoid hydration errors - In SPA mode (
ssr: false),clientLoaderis the primary data loading mechanism
関連
- ./spa.md
- ./fetchers.md
Error Boundary
Error boundaries automatically catch errors thrown in loaders, actions, and components, rendering the nearest ErrorBoundary instead of a blank page.
Available in: Framework mode and Data mode.
実装方法
1. Export an ErrorBoundary function from a route module (Framework mode) or pass ErrorBoundary to a route object (Data mode) 2. Handle three cases: isRouteErrorResponse, instanceof Error, and unknown thrown values 3. Add nested ErrorBoundary exports to isolate errors within specific route subtrees 4. Use throw data("Not Found", { status: 404 }) in loaders/actions for expected errors
コード例
// app/root.tsx (Framework mode)
import { isRouteErrorResponse } from "react-router";
import type { Route } from "./+types/root";
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
if (isRouteErrorResponse(error)) {
return <h1>{error.status} {error.statusText}</h1>;
} else if (error instanceof Error) {
return <p>{error.message}</p>;
} else {
return <h1>Unknown Error</h1>;
}
}
// Throw a 404 from a loader
import { data } from "react-router";
export async function loader({ params }: Route.LoaderArgs) {
const record = await fakeDb.getRecord(params.id);
if (!record) throw data("Record Not Found", { status: 404 });
return record;
}注意点
- In production (Framework mode), server errors are sanitized before reaching the browser — stack traces are not exposed
- Data thrown via
throw data()is NOT sanitized and will reach the client - Errors bubble up to the nearest ancestor
ErrorBoundaryif the current route doesn't export one
関連
- ./error-reporting.md
- ./form-validation.md
- ./status.md
Error Reporting
Send errors to external monitoring services from both the server and the client. React Router provides dedicated hooks separate from ErrorBoundary for this purpose.
実装方法
Server errors (Framework mode): 1. Run react-router reveal entry.server to create entry.server.tsx 2. Export a handleError function
Client errors (Framework mode): 1. Run react-router reveal entry.client to create entry.client.tsx 2. Pass an onError callback to HydratedRouter
Client errors (Data mode): Pass an onError callback to RouterProvider.
コード例
// entry.server.tsx — server error reporting
import { type HandleErrorFunction } from "react-router";
export const handleError: HandleErrorFunction = (error, { request }) => {
if (!request.signal.aborted) {
myReportError(error);
console.error(error);
}
};// entry.client.tsx — client error reporting (Framework mode)
import { type ClientOnErrorFunction } from "react-router";
const onError: ClientOnErrorFunction = (error, { location, errorInfo }) => {
myReportError(error, location, errorInfo);
};
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter onError={onError} />
</StrictMode>,
);
});注意点
- Check
request.signal.abortedon the server to avoid logging interrupted (cancelled) requests handleErroris Framework mode only;onErrorworks in both Framework and Data modes
関連
- ./error-boundary.md
Fetchers
Fetchers allow loading and mutating data without causing a navigation. Each fetcher tracks its own independent state and is ideal for concurrent interactions such as inline editing, search comboboxes, and optimistic UI.
Available in: Framework mode and Data mode.
実装方法
1. Call useFetcher() to get a fetcher instance 2. Use fetcher.Form or fetcher.submit() to trigger an action or loader 3. Read fetcher.state ("idle" | "submitting" | "loading") for pending UI 4. Read fetcher.formData for optimistic UI during submission 5. Read fetcher.data for the response from the action/loader
コード例
import { useFetcher } from "react-router";
export default function Component() {
const fetcher = useFetcher();
// Optimistic title while submitting
const title = fetcher.formData?.get("title") || loaderData.title;
return (
<div>
<h1>{title}</h1>
<fetcher.Form method="post">
<input type="text" name="title" />
{fetcher.state !== "idle" && <p>Saving...</p>}
{fetcher.data?.error && (
<p style={{ color: "red" }}>{fetcher.data.error}</p>
)}
<button type="submit">Save</button>
</fetcher.Form>
</div>
);
}注意点
- Fetchers do not change the URL or navigate between routes
- Submitting to an action automatically revalidates loader data
- Use
import type { loader }withuseFetcher<typeof loader>()for type-safe loader responses - Call
fetcher.submit(form)to submit programmatically (e.g., on input change for search)
関連
- ./form-validation.md
- ./navigation-blocking.md
File Route Conventions
The @react-router/fs-routes package enables file-system based routing where filenames in app/routes/ automatically map to URL paths.
Available in: Framework mode only.
実装方法
1. Install: npm i @react-router/fs-routes 2. Use flatRoutes() in app/routes.ts 3. Name files following the conventions below
コード例
// app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;| Filename | URL |
|---|---|
_index.tsx | / |
about.tsx | /about |
concerts.$city.tsx | /concerts/:city |
concerts._index.tsx | /concerts |
_auth.login.tsx | /login (nested under _auth.tsx layout) |
concerts_.mine.tsx | /concerts/mine (no layout nesting) |
($lang)._index.tsx | / or /:lang |
$.tsx | catch-all |
sitemap[.]xml.tsx | /sitemap.xml |
// Folder routes: app/routes/concerts.$city/route.tsx
export async function loader({ params }) {
return fakeDb.getAllConcertsForCity(params.city);
}注意点
- Supported extensions:
.js,.jsx,.ts,.tsx - Files inside a folder route (other than
route.tsx) are NOT treated as routes - For catch-all routes, return
data({}, 404)from the loader for a proper 404 response - Configure
ignoredRouteFilesorrootDirectoryoptions inflatRoutes()as needed
関連
- ./resource-routes.md
- ./route-module-type-safety.md
File Uploads
Handle multipart form file uploads in React Router using @remix-run/form-data-parser for streaming support and optional @remix-run/file-storage for persistence.
Available in: Framework mode only.
実装方法
1. Set encType="multipart/form-data" on the form 2. Install @remix-run/form-data-parser 3. Use parseFormData(request, uploadHandler) in the action 4. Process the FileUpload object immediately (it is streaming data) 5. Optionally use @remix-run/file-storage to persist files locally
コード例
// api/avatar.tsx
import { type FileUpload, parseFormData } from "@remix-run/form-data-parser";
import { LocalFileStorage } from "@remix-run/file-storage/local";
import type { Route } from "./+types/avatar";
const fileStorage = new LocalFileStorage("./uploads/avatars");
export async function action({ request, params }: Route.ActionArgs) {
const formData = await parseFormData(request, async (fileUpload: FileUpload) => {
if (fileUpload.fieldName === "avatar" && fileUpload.type.startsWith("image/")) {
const key = `user-${params.id}-avatar`;
await fileStorage.set(key, fileUpload);
return fileStorage.get(key);
}
});
const file = formData.get("avatar");
// ...
}
export default function Component() {
return (
<form method="post" encType="multipart/form-data">
<input type="file" name="avatar" />
<button>Upload</button>
</form>
);
}注意点
FileUploadobjects are temporary streaming data — store them immediately before accessing other form fields- The stored file is a
LazyFile: content is only read when accessed (e.g.,file.stream()), avoiding unnecessary memory usage - Serve stored files via a resource route that returns
new Response(file.stream(), { headers: { "Content-Type": file.type } })
関連
- ./resource-routes.md
- ./form-validation.md
Form Validation
Validate form input server-side in an action and return errors to the UI without navigating away. Use useFetcher to keep users on the form while showing inline errors.
Available in: Framework mode and Data mode.
実装方法
1. Use useFetcher and fetcher.Form so submission doesn't navigate 2. Return data({ errors }, { status: 400 }) from the action when validation fails 3. Read fetcher.data?.errors in the component to display inline messages 4. Return redirect("/destination") on success
コード例
import { data, redirect, useFetcher } from "react-router";
import type { Route } from "./+types/signup";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const email = String(formData.get("email"));
const password = String(formData.get("password"));
const errors: Record<string, string> = {};
if (!email.includes("@")) errors.email = "Invalid email address";
if (password.length < 12) errors.password = "Password must be 12+ characters";
if (Object.keys(errors).length > 0) {
return data({ errors }, { status: 400 });
}
return redirect("/dashboard");
}
export default function Signup() {
const fetcher = useFetcher<typeof action>();
const errors = fetcher.data?.errors;
return (
<fetcher.Form method="post">
<input type="email" name="email" />
{errors?.email && <em>{errors.email}</em>}
<input type="password" name="password" />
{errors?.password && <em>{errors.password}</em>}
<button type="submit">Sign Up</button>
</fetcher.Form>
);
}注意点
- Use
status: 400— React Router only triggers data revalidation on 2xx responses, so a 400 keeps the user on the form without reloading route data - Returning
data()instead of throwing means theErrorBoundaryis NOT triggered
関連
- ./fetchers.md
- ./error-boundary.md
- ./status.md
Headers
Set HTTP response headers from route modules using the headers export. Headers can be static, derived from loader/action data, or merged from parent routes.
Available in: Framework mode only.
実装方法
1. Export a headers function from a route module returning a Headers instance or plain object 2. Use loaderHeaders / actionHeaders args to forward dynamic headers set via data() 3. Use parentHeaders to merge or override ancestor route headers 4. For app-wide headers, add them in entry.server.tsx via handleRequest
コード例
// Static headers
export function headers(_: Route.HeadersArgs) {
return {
"Cache-Control": "max-age=3600, s-maxage=86400",
"X-Frame-Options": "DENY",
};
}
// Dynamic headers from loader
import { data } from "react-router";
export async function loader({ params }: Route.LoaderArgs) {
const [page, ms] = await fakeTimeCall(getPage(params.id));
return data(page, {
headers: { "Server-Timing": `page;dur=${ms}` },
});
}
export function headers({ loaderHeaders }: Route.HeadersArgs) {
return loaderHeaders;
}
// Merging parent headers
export function headers({ parentHeaders }: Route.HeadersArgs) {
parentHeaders.set("Cache-Control", "max-age=3600");
return parentHeaders;
}注意点
Set-Cookieheaders are automatically preserved from parent routes without an explicitheadersexport- Best practice: define headers only in leaf routes to avoid complex merging
react-router revealgeneratesentry.server.tsxfor global header configuration
関連
- ./status.md
- ./security.md
Instrumentation
Add observability (logging, error reporting, performance tracing) by wrapping request handlers and route functions without modifying runtime logic.
Status: Experimental (unstable_instrumentations) — subject to breaking changes.
実装方法
Server (Framework mode): Export unstable_instrumentations array from entry.server.tsx.
Client (Framework mode): Pass unstable_instrumentations prop to <HydratedRouter>.
Data mode: Pass unstable_instrumentations option to createBrowserRouter().
コード例
// entry.server.tsx
export const unstable_instrumentations = [
{
handler(handler) {
handler.instrument({
async request(handleRequest, { request }) {
console.log(`→ ${request.method} ${request.url}`);
await handleRequest();
console.log(`← ${request.url}`);
},
});
},
route(route) {
route.instrument({
async loader(callLoader, { request }) {
const start = Date.now();
await callLoader();
console.log(`loader ${route.id} (${Date.now() - start}ms)`);
},
});
},
},
];// entry.client.tsx
<HydratedRouter
unstable_instrumentations={[
{
router(router) {
router.instrument({
async navigate(callNavigate, { to }) {
console.log(`navigate → ${to}`);
await callNavigate();
},
});
},
},
]}
/>注意点
- Instrumentation is read-only: cannot modify arguments or return values
- Errors thrown inside instrumentation are caught internally and will not break the app
- Available hooks — Handler:
request; Router:navigate,fetch; Route:loader,action,middleware,lazy - Use conditional instrumentation to avoid overhead in production
関連
- ./middleware.md
- ./error-reporting.md
Middleware
Run code before and after response generation for matched routes. Enables authentication, logging, response header injection, and shared context in a reusable, composable way.
Status: Future flag v8_middleware required (Framework mode). In Data mode, augment the Future interface instead.
Setup
Framework Mode
// react-router.config.ts
export default { future: { v8_middleware: true } } satisfies Config;Data Mode
// src/react-router.d.ts
import "react-router";
declare module "react-router" {
interface Future { v8_middleware: true; }
}Execution Order
Middleware runs parent → child on the way down, then child → parent on the way up after the Response is generated:
Root middleware start
Parent middleware start
Child middleware start
Run loaders → generate Response
Child middleware end
Parent middleware end
Root middleware endServer Middleware
// app/context.ts
import { createContext } from "react-router";
export const userContext = createContext<User | null>(null);// routes/dashboard.tsx
import { redirect } from "react-router";
import { userContext } from "~/context";
async function authMiddleware({ request, context }) {
const user = await getUserFromSession(request);
if (!user) throw redirect("/login");
context.set(userContext, user);
}
export const middleware: Route.MiddlewareFunction[] = [authMiddleware];
export async function loader({ context }: Route.LoaderArgs) {
const user = context.get(userContext);
return { user };
}Client Middleware
Runs in the browser for every client-side navigation, regardless of whether loaders exist.
async function timingMiddleware({ request }, next) {
const start = performance.now();
await next();
console.log(`Navigation took ${performance.now() - start}ms`);
}
export const clientMiddleware: Route.ClientMiddlewareFunction[] = [
timingMiddleware,
];Client middleware can inspect loader/action results via the return value of next():
async function cmsFallbackMiddleware({ request }, next) {
const results = await next();
// results is Record<string, DataStrategyResult> keyed by route id
const found404 = Object.values(results).some(
(r) => isRouteErrorResponse(r.result) && r.result.status === 404,
);
if (found404) {
const cmsRedirect = await checkCMSRedirects(request.url);
if (cmsRedirect) throw redirect(cmsRedirect, 302);
}
}getLoadContext Migration
When middleware is enabled, context changes from a plain object to a RouterContextProvider instance.
// Before (without middleware)
function getLoadContext(req, res) {
return { db: createDb() };
}
// After (with middleware)
import { createContext, RouterContextProvider } from "react-router";
const dbContext = createContext<Database>();
function getLoadContext(req, res) {
const context = new RouterContextProvider();
context.set(dbContext, createDb());
return context;
}Notes
- Server middleware must return the
Responsefromnext(); client middleware typically does not next()can only be called once per middleware function- Server middleware runs on document requests and
.datarequests, but not on client-side navigations unless a loader/action exists. Add an emptyloaderto force execution on every navigation - Middleware errors are caught by the nearest
ErrorBoundary;next()never throws AsyncLocalStorageis compatible with server middleware and works well with RSC
Related
- ./instrumentation.md
- ./headers.md
- ./security.md
- ../utils/createContext.md
- ../utils/RouterContextProvider.md
Navigation Blocking
Prevent accidental navigation away from a page when users have unsaved changes, and show a custom confirmation dialog using the useBlocker hook.
Available in: Framework mode and Data mode.
実装方法
1. Track dirty state with useState 2. Update dirty state on form onChange 3. Call useBlocker(callback) — return true to block, false to allow 4. Render a confirmation UI when blocker.state === "blocked" 5. Call blocker.proceed() to allow or blocker.reset() to cancel navigation 6. Reset or proceed in a useEffect after successful form submission
コード例
import { useState, useCallback, useEffect } from "react";
import { useFetcher, useBlocker } from "react-router";
export default function Contact() {
const [isDirty, setIsDirty] = useState(false);
const fetcher = useFetcher();
const blocker = useBlocker(useCallback(() => isDirty, [isDirty]));
useEffect(() => {
if (fetcher.data?.ok) {
if (blocker.state === "blocked") blocker.proceed();
}
}, [fetcher.data]);
return (
<>
<fetcher.Form
method="post"
onChange={(e) => setIsDirty(Boolean(e.currentTarget.message.value))}
>
<textarea name="message" />
<button type="submit">Send</button>
</fetcher.Form>
{blocker.state === "blocked" && (
<div>
<p>You have unsaved changes. Leave anyway?</p>
<button onClick={() => blocker.proceed()}>Leave</button>
<button onClick={() => blocker.reset()}>Stay</button>
</div>
)}
</>
);
}注意点
useBlockeronly blocks client-side navigations; it cannot block browser back/forward or direct URL changes in all browsers- The callback passed to
useBlockershould be stable (useuseCallback) to avoid re-registering on every render
関連
- ./fetchers.md
- ./form-validation.md
Pre-Rendering
Render pages at build time instead of runtime to speed up initial loads for static content. Pre-rendered routes use the same loader functions as SSR routes.
Available in: Framework mode only.
実装方法
1. Add a prerender key to react-router.config.ts 2. Set it to true (all static paths), an array of paths, or an async function returning paths 3. Configure ssr: true (default) to keep a runtime server alongside pre-rendered pages, or ssr: false to deploy a fully static site
コード例
// react-router.config.ts
// Pre-render all static routes
export default { prerender: true } satisfies Config;
// Pre-render specific paths including dynamic ones
const slugs = getPostSlugs();
export default {
prerender: ["/", "/blog", ...slugs.map((s) => `/blog/${s}`)],
} satisfies Config;
// Async function with CMS data
export default {
async prerender({ getStaticPaths }) {
const slugs = await getPostSlugsFromCMS();
return [
...getStaticPaths(),
...slugs.map((s) => `/blog/${s}`),
];
},
} satisfies Config;
// Parallel pre-rendering with concurrency control
export default {
prerender: {
paths: ["/", "/blog"],
concurrency: 4,
},
} satisfies Config;注意点
- Build output:
[url].htmlfor document requests,[url].datafor client-side navigations - With
ssr: false,headersandactionexports are not allowed (no runtime server) - Pre-rendered files are only saved during
react-router build, not during development - For a static host SPA fallback, add a
_redirectsrule:/* /index.html 200
関連
- ./spa.md
- ./server-bundles.md
Presets
Presets package React Router configuration so that tools and hosting providers can configure the router on behalf of users. A preset can set config options and validate that they haven't been overridden.
Available in: Framework mode only.
実装方法
1. Create a function that returns a Preset object with name, reactRouterConfig, and optionally reactRouterConfigResolved 2. Publish the preset to npm for reuse 3. Import it and add to the presets array in react-router.config.ts
コード例
// my-preset.ts
import type { Preset } from "@react-router/dev/config";
export function myCoolPreset(): Preset {
return {
name: "my-cool-preset",
reactRouterConfig: () => ({
serverBundles: ({ branch }) => {
const isAuth = branch.some((r) =>
r.id.split("/").includes("_authenticated"),
);
return isAuth ? "authenticated" : "unauthenticated";
},
}),
reactRouterConfigResolved: ({ reactRouterConfig }) => {
// Throw if the user overrode a required config
},
};
}// react-router.config.ts
import { myCoolPreset } from "react-router-preset-cool";
export default {
presets: [myCoolPreset()],
} satisfies Config;注意点
- Configs are merged in definition order; user-specified config always takes final precedence
- Only use
reactRouterConfigResolvedwhen overriding would cause a hard error - Designed to be published to npm, not written inline in user projects
関連
- ./server-bundles.md
React Server Components
Experimental support for React Server Components (RSC), enabling server-side rendering with direct data access, server functions, and mixed server/client component trees.
Status: Experimental — subject to breaking changes in minor/patch releases. Requires React 19+. Available in: Framework Mode and Data Mode (not Declarative Mode).
Quick Start Templates
# Framework Mode
npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-framework-mode
# Data Mode (Vite)
npx create-react-router@latest --template remix-run/react-router-templates/unstable_rsc-data-mode-viteRSC Framework Mode
Vite Configuration
// vite.config.ts
import { unstable_reactRouterRSC as reactRouterRSC } from "@react-router/dev/vite";
import rsc from "@vitejs/plugin-rsc";
// Note: rsc() must be placed AFTER reactRouterRSC()
export default defineConfig({ plugins: [reactRouterRSC(), rsc()] });Route Server Components
Export ServerComponent instead of default:
// routes/home.tsx
export async function loader() {
return { message: await getMessage() };
}
export function ServerComponent({ loaderData }: Route.ServerComponentProps) {
return <h1>{loaderData.message}</h1>;
}Server/client export counterparts:
| Server Export | Client Export |
|---|---|
ServerComponent | default |
ServerErrorBoundary | ErrorBoundary |
ServerLayout | Layout |
ServerHydrateFallback | HydrateFallback |
Client Components
// counter.tsx
"use client";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}Custom Entry Files (auto-detected in app/)
app/entry.rsc.ts— Custom RSC server entryapp/entry.ssr.ts— Custom SSR server entryapp/entry.client.tsx— Custom client entry
Unsupported Config Options (RSC Framework Mode)
buildEnd, presets, serverBundles, future.v8_splitRouteModules, subResourceIntegrity
RSC Data Mode
Lower-level APIs for custom framework integration using matchRSCServerRequest, routeRSCServerRequest, getRSCStream, and createCallServer. See the RSC API reference for details.
Notes
.server/.clientmodule conventions are NOT supported in RSC Framework Mode; use"server-only"from@vitejs/plugin-rscinstead- Loaders and actions can return React elements directly in RSC mode
AsyncLocalStorageworks with server middleware in RSC mode via the middleware integration
Related
- ./spa.md
- ./client-data.md
- ./middleware.md