Push V1 app

This commit is contained in:
jlacoste
2026-06-26 11:54:29 +02:00
parent 8b7caa1a5a
commit 9d1990523f
3881 changed files with 1291493 additions and 1 deletions
+138
View File
@@ -0,0 +1,138 @@
---
title: Actions
order: 5
---
# Actions
[MODES: data]
## Defining Actions
Data mutations are done through Route actions defined on the `action` property of a route object. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it.
```tsx
import { createBrowserRouter } from "react-router";
import { someApi } from "./api";
let router = createBrowserRouter([
{
path: "/projects/:projectId",
Component: Project,
action: async ({ request }) => {
let formData = await request.formData();
let title = formData.get("title");
let project = await someApi.updateProject({ title });
return project;
},
},
]);
```
## Calling Actions
Actions are called declaratively through `<Form>` and imperatively through `useSubmit` (or `<fetcher.Form>` and `fetcher.submit`) by referencing the route's path and a "post" method.
### Calling actions with a Form
```tsx
import { Form } from "react-router";
function SomeComponent() {
return (
<Form action="/projects/123" method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
);
}
```
This will cause a navigation and a new entry will be added to the browser history.
### Calling actions with useSubmit
You can submit form data to an action imperatively with `useSubmit`.
```tsx
import { useCallback } from "react";
import { useSubmit } from "react-router";
import { useFakeTimer } from "fake-lib";
function useQuizTimer() {
let submit = useSubmit();
let cb = useCallback(() => {
submit(
{ quizTimedOut: true },
{ action: "/end-quiz", method: "post" },
);
}, []);
let tenMinutes = 10 * 60 * 1000;
useFakeTimer(tenMinutes, cb);
}
```
This will cause a navigation and a new entry will be added to the browser history.
### Calling actions with a fetcher
Fetchers allow you to submit data to actions (and loaders) without causing a navigation (no new entries in the browser history).
```tsx
import { useFetcher } from "react-router";
function Task() {
let fetcher = useFetcher();
let busy = fetcher.state !== "idle";
return (
<fetcher.Form method="post" action="/update-task/123">
<input type="text" name="title" />
<button type="submit">
{busy ? "Saving..." : "Save"}
</button>
</fetcher.Form>
);
}
```
They also have the imperative `submit` method.
```tsx
fetcher.submit(
{ title: "New Title" },
{ action: "/update-task/123", method: "post" },
);
```
See the [Using Fetchers][fetchers] guide for more information.
## Accessing Action Data
Actions can return data available through `useActionData` in the route component or `fetcher.data` when using a fetcher.
```tsx
function Project() {
let actionData = useActionData();
return (
<div>
<h1>Project</h1>
<Form method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
{actionData ? (
<p>{actionData.title} updated</p>
) : null}
</div>
);
}
```
---
Next: [Navigating](./navigating)
[fetchers]: ../../how-to/fetchers
+198
View File
@@ -0,0 +1,198 @@
---
title: Custom Framework
order: 8
---
# Custom Framework
[MODES: data]
## Introduction
Instead of using `@react-router/dev`, you can integrate React Router's framework features (like loaders, actions, fetchers, etc.) into your own bundler and server abstractions with Data Mode.
## Client Rendering
### 1. Create a Router
The browser runtime API that enables route module APIs (loaders, actions, etc.) is `createBrowserRouter`.
It takes an array of route objects that support loaders, actions, error boundaries and more. The React Router Vite plugin creates one of these from `routes.ts`, but you can create one manually (or with an abstraction) and use your own bundler.
```tsx
import { createBrowserRouter } from "react-router";
let router = createBrowserRouter([
{
path: "/",
Component: Root,
children: [
{
path: "shows/:showId",
Component: Show,
loader: ({ request, params }) =>
fetch(`/api/show/${params.showId}.json`, {
signal: request.signal,
}),
},
],
},
]);
```
### 2. Render the Router
To render the router in the browser, use `<RouterProvider>`.
```tsx
import {
createBrowserRouter,
RouterProvider,
} from "react-router";
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />,
);
```
### 3. Lazy Loading
Routes can take most of their definition lazily with the `lazy` property.
```tsx
createBrowserRouter([
{
path: "/show/:showId",
lazy: {
loader: async () =>
(await import("./show.loader.js")).loader,
action: async () =>
(await import("./show.action.js")).action,
Component: async () =>
(await import("./show.component.js")).Component,
},
},
]);
```
## Server Rendering
To server render a custom setup, there are a few server APIs available for rendering and data loading.
This guide simply gives you some ideas about how it works. For deeper understanding, please see the [Custom Framework Example Repo](https://github.com/remix-run/custom-react-router-framework-example)
### 1. Define Your Routes
Routes are the same kinds of objects on the server as the client.
```tsx
export default [
{
path: "/",
Component: Root,
children: [
{
path: "shows/:showId",
Component: Show,
loader: ({ params }) => {
return db.loadShow(params.id);
},
},
],
},
];
```
### 2. Create a static handler
Turn your routes into a request handler with `createStaticHandler`:
```tsx
import { createStaticHandler } from "react-router";
import routes from "./some-routes";
let { query, dataRoutes } = createStaticHandler(routes);
```
### 3. Get Routing Context and Render
React Router works with web fetch [Requests](https://developer.mozilla.org/en-US/docs/Web/API/Request), so if your server doesn't, you'll need to adapt whatever objects it uses to a web fetch `Request` object.
This step assumes your server receives `Request` objects.
```tsx
import { renderToString } from "react-dom/server";
import {
createStaticHandler,
createStaticRouter,
StaticRouterProvider,
} from "react-router";
import routes from "./some-routes.js";
let { query, dataRoutes } = createStaticHandler(routes);
export async function handler(request: Request) {
// 1. run actions/loaders to get the routing context with `query`
let context = await query(request);
// If `query` returns a Response, send it raw (a route probably a redirected)
if (context instanceof Response) {
return context;
}
// 2. Create a static router for SSR
let router = createStaticRouter(dataRoutes, context);
// 3. Render everything with StaticRouterProvider
let html = renderToString(
<StaticRouterProvider
router={router}
context={context}
/>,
);
// Setup headers from action and loaders from deepest match
let leaf = context.matches[context.matches.length - 1];
let actionHeaders = context.actionHeaders[leaf.route.id];
let loaderHeaders = context.loaderHeaders[leaf.route.id];
let headers = new Headers(actionHeaders);
if (loaderHeaders) {
for (let [key, value] of loaderHeaders.entries()) {
headers.append(key, value);
}
}
headers.set("Content-Type", "text/html; charset=utf-8");
// 4. send a response
return new Response(`<!DOCTYPE html>${html}`, {
status: context.statusCode,
headers,
});
}
```
### 4. Hydrate in the browser
Hydration data is embedded onto `window.__staticRouterHydrationData`, use that to initialize your client side router and render a `<RouterProvider>`.
```tsx
import { StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { RouterProvider } from "react-router/dom";
import routes from "./app/routes.js";
import { createBrowserRouter } from "react-router";
let router = createBrowserRouter(routes, {
hydrationData: window.__staticRouterHydrationData,
});
hydrateRoot(
document,
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
```
+44
View File
@@ -0,0 +1,44 @@
---
title: Data Loading
order: 4
---
# Data Loading
[MODES: data]
## Providing Data
Data is provided to route components from route loaders:
```tsx
createBrowserRouter([
{
path: "/",
loader: async () => {
// return data from here
return { records: await getSomeRecords() };
},
Component: MyRoute,
},
]);
```
## Accessing Data
The data is available in route components with `useLoaderData`.
```tsx
import { useLoaderData } from "react-router";
function MyRoute() {
const { records } = useLoaderData();
return <div>{records.length}</div>;
}
```
As the user navigates between routes, the loaders are called before the route component is rendered.
---
Next: [Actions](./actions)
+4
View File
@@ -0,0 +1,4 @@
---
title: Data Mode
order: 3
---
+56
View File
@@ -0,0 +1,56 @@
---
title: Installation
order: 1
---
# Installation
[MODES: data]
## Bootstrap with a Bundler Template
You can start with a React template from Vite and choose "React", otherwise bootstrap your application however you prefer (Parcel, Webpack, etc).
```shellscript nonumber
npx create-vite@latest
```
## Install React Router
Next install React Router from npm:
```shellscript nonumber
npm i react-router
```
## Create a Router and Render
Create a router and pass it to `RouterProvider`:
```tsx lines=[3-4,6-11,16]
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
const router = createBrowserRouter([
{
path: "/",
element: <div>Hello World</div>,
},
]);
const root = document.getElementById("root");
ReactDOM.createRoot(root).render(
<RouterProvider router={router} />,
);
```
<docs-info>Data Routers should not be held in React state. You should create your router
once outside of the React tree and pass it to `<RouterProvider>`. You can use
`patchRoutesOnNavigation` to add additional routes programmatically.</docs-info>
---
Next: [Routing](./routing)
+12
View File
@@ -0,0 +1,12 @@
---
title: Navigating
order: 6
---
# Navigating
Navigating in Data Mode is the same as Framework Mode, please see the [Navigating](../framework/navigating) guide for more information.
---
Next: [Pending UI](./pending-ui)
+12
View File
@@ -0,0 +1,12 @@
---
title: Pending UI
order: 7
---
# Pending UI
Pending UI is the same as Framework Mode, please see the [Pending UI](../framework/pending-ui) guide for more information.
---
Next: [Custom Framework](./custom)
+268
View File
@@ -0,0 +1,268 @@
---
title: Route Object
order: 3
---
# Route Object
[MODES: data]
## Introduction
The objects passed to `createBrowserRouter` are called Route Objects.
```tsx lines=[2-5]
createBrowserRouter([
{
path: "/",
Component: App,
},
]);
```
Route modules are the foundation of React Router's data features, they define:
- data loading
- actions
- revalidation
- error boundaries
- and more
This guide is a quick overview of every route object feature.
## Component
The `Component` property in a route object defines the component that will render when the route matches.
```tsx lines=[4]
createBrowserRouter([
{
path: "/",
Component: MyRouteComponent,
},
]);
function MyRouteComponent() {
return (
<div>
<h1>Look ma!</h1>
<p>
I'm still using React Router after like 10 years.
</p>
</div>
);
}
```
## `middleware`
Route [middleware][middleware] runs sequentially before and after navigations. This gives you a singular place to do things like logging and authentication. The `next` function continues down the chain, and on the leaf route the `next` function executes the loaders/actions for the navigation.
```tsx
createBrowserRouter([
{
path: "/",
middleware: [loggingMiddleware],
loader: rootLoader,
Component: Root,
children: [{
path: 'auth',
middleware: [authMiddleware],
loader: authLoader,
Component: Auth,
children: [...]
}]
},
]);
async function loggingMiddleware({ request }, next) {
let url = new URL(request.url);
console.log(`Starting navigation: ${url.pathname}${url.search}`);
const start = performance.now();
await next();
const duration = performance.now() - start;
console.log(`Navigation completed in ${duration}ms`);
}
const userContext = createContext<User>();
async function authMiddleware ({ context }) {
const userId = getUserId();
if (!userId) {
throw redirect("/login");
}
context.set(userContext, await getUserById(userId));
};
```
See also:
- [Middleware][middleware]
## `loader`
Route loaders provide data to route components before they are rendered.
```tsx
import {
useLoaderData,
createBrowserRouter,
} from "react-router";
createBrowserRouter([
{
path: "/",
loader: loader,
Component: MyRoute,
},
]);
async function loader({ params }) {
return { message: "Hello, world!" };
}
function MyRoute() {
let data = useLoaderData();
return <h1>{data.message}</h1>;
}
```
See also:
- [`loader` params][loader-params]
## `action`
Route actions allow server-side data mutations with automatic revalidation of all loader data on the page when called from `<Form>`, `useFetcher`, and `useSubmit`.
```tsx
import {
createBrowserRouter,
useLoaderData,
useActionData,
Form,
} from "react-router";
import { TodoList } from "~/components/TodoList";
createBrowserRouter([
{
path: "/items",
action: action,
loader: loader,
Component: Items,
},
]);
async function action({ request }) {
const data = await request.formData();
const todo = await fakeDb.addItem({
title: data.get("title"),
});
return { ok: true };
}
// this data will be revalidated after the action completes...
async function loader() {
const items = await fakeDb.getItems();
return { items };
}
// ...so that the list here is updated automatically
export default function Items() {
let data = useLoaderData();
return (
<div>
<List items={data.items} />
<Form method="post" navigate={false}>
<input type="text" name="title" />
<button type="submit">Create Todo</button>
</Form>
</div>
);
}
```
## `shouldRevalidate`
Loader data is automatically revalidated after certain events like navigations and form submissions.
This hook enables you to opt in or out of the default revalidation behavior. The default behavior is nuanced to avoid calling loaders unnecessarily.
A route loader is revalidated when:
- its own route params change
- any change to URL search params
- after an action is called and returns a non-error status code
By defining this function, you opt out of the default behavior completely and can manually control when loader data is revalidated for navigations and form submissions.
```tsx
import type { ShouldRevalidateFunctionArgs } from "react-router";
function shouldRevalidate(
arg: ShouldRevalidateFunctionArgs,
) {
return true; // false
}
createBrowserRouter([
{
path: "/",
shouldRevalidate: shouldRevalidate,
Component: MyRoute,
},
]);
```
[`ShouldRevalidateFunctionArgs` Reference Documentation ↗](https://api.reactrouter.com/v7/interfaces/react-router.ShouldRevalidateFunctionArgs.html)
Please note the default behavior is different in [Framework Mode](../modes).
## `lazy`
Most properties can be lazily imported to reduce the initial bundle size.
```tsx
createBrowserRouter([
{
path: "/app",
lazy: async () => {
// load component and loader in parallel before rendering
const [Component, loader] = await Promise.all([
import("./app"),
import("./app-loader"),
]);
return { Component, loader };
},
},
]);
```
## `handle`
Route handle allows apps to add anything to a route match in `useMatches` to create abstractions (like breadcrumbs, etc.).
```tsx
createBrowserRouter([
{
path: "/app",
handle: {
breadcrumb: "App",
},
},
]);
```
See also:
- [`useMatches`][use-matches]
---
Next: [Data Loading](./data-loading)
[loader-params]: https://api.reactrouter.com/v7/interfaces/react-router.LoaderFunctionArgs
[middleware]: ../../how-to/middleware
[use-matches]: ../../api/hooks/useMatches
+281
View File
@@ -0,0 +1,281 @@
---
title: Routing
order: 2
---
# Routing
[MODES: data]
## Configuring Routes
Routes are configured as the first argument to `createBrowserRouter`. At a minimum, you need a path and component:
```tsx
import { createBrowserRouter } from "react-router";
function Root() {
return <h1>Hello world</h1>;
}
const router = createBrowserRouter([
{ path: "/", Component: Root },
]);
```
Here is a larger sample route config:
```ts filename=app/routes.ts
createBrowserRouter([
{
path: "/",
Component: Root,
children: [
{ index: true, Component: Home },
{ path: "about", Component: About },
{
path: "auth",
Component: AuthLayout,
children: [
{ path: "login", Component: Login },
{ path: "register", Component: Register },
],
},
{
path: "concerts",
children: [
{ index: true, Component: ConcertsHome },
{ path: ":city", Component: ConcertsCity },
{ path: "trending", Component: ConcertsTrending },
],
},
],
},
]);
```
## Route Objects
Route objects define the behavior of a route beyond just the path and component, like data loading and actions. We'll go into more detail in the [Route Object guide](./route-object), but here's a quick example of a loader.
```tsx filename=app/team.tsx
import {
createBrowserRouter,
useLoaderData,
} from "react-router";
createBrowserRouter([
{
path: "/teams/:teamId",
loader: async ({ params }) => {
let team = await fetchTeam(params.teamId);
return { name: team.name };
},
Component: Team,
},
]);
function Team() {
let data = useLoaderData();
return <h1>{data.name}</h1>;
}
```
## Nested Routes
Routes can be nested inside parent routes through `children`.
```ts filename=app/routes.ts
createBrowserRouter([
{
path: "/dashboard",
Component: Dashboard,
children: [
{ index: true, Component: Home },
{ path: "settings", Component: Settings },
],
},
]);
```
The path of the parent is automatically included in the child, so this config creates both `"/dashboard"` and `"/dashboard/settings"` URLs.
Child routes are rendered through the `<Outlet/>` in the parent route.
```tsx filename=app/dashboard.tsx
import { Outlet } from "react-router";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* will either be <Home> or <Settings> */}
<Outlet />
</div>
);
}
```
## Layout Routes
Omitting the `path` in a route creates new [Nested Routes](#nested-routes) for its children without adding any segments to the URL.
```tsx lines=[3,16]
createBrowserRouter([
{
// no path on this parent route, just the component
Component: MarketingLayout,
children: [
{ index: true, Component: Home },
{ path: "contact", Component: Contact },
],
},
{
path: "projects",
children: [
{ index: true, Component: ProjectsHome },
{
// again, no path, just a component for the layout
Component: ProjectLayout,
children: [
{ path: ":pid", Component: Project },
{ path: ":pid/edit", Component: EditProject },
],
},
],
},
]);
```
Note that:
- `Home` and `Contact` will be rendered into the `MarketingLayout` outlet
- `Project` and `EditProject` will be rendered into the `ProjectLayout` outlet while `ProjectsHome` will not.
## Index Routes
Index routes are defined by setting `index: true` on a route object without a path.
```ts
{ index: true, Component: Home }
```
Index routes render into their parent's [Outlet][outlet] at their parent's URL (like a default child route).
```ts lines=[4,5,10,11]
import { createBrowserRouter } from "react-router";
createBrowserRouter([
// renders at "/"
{ index: true, Component: Home },
{
Component: Dashboard,
path: "/dashboard",
children: [
// renders at "/dashboard"
{ index: true, Component: DashboardHome },
{ path: "settings", Component: DashboardSettings },
],
},
]);
```
Note that index routes can't have children.
## Prefix Route
A route with just a path and no component creates a group of routes with a path prefix.
```tsx lines=[3]
createBrowserRouter([
{
// no component, just a path
path: "/projects",
children: [
{ index: true, Component: ProjectsHome },
{ path: ":pid", Component: Project },
{ path: ":pid/edit", Component: EditProject },
],
},
]);
```
This creates the routes `/projects`, `/projects/:pid`, and `/projects/:pid/edit` without introducing a layout component.
## Dynamic Segments
If a path segment starts with `:` then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as `params` to other router APIs.
```ts lines=[2]
{
path: "teams/:teamId",
loader: async ({ params }) => {
// params are available in loaders/actions
let team = await fetchTeam(params.teamId);
return { name: team.name };
},
Component: Team,
}
```
```tsx
import { useParams } from "react-router";
function Team() {
// params are available in components through useParams
let params = useParams();
// ...
}
```
You can have multiple dynamic segments in one route path:
```ts
{
path: "c/:categoryId/p/:productId";
}
```
## Optional Segments
You can make a route segment optional by adding a `?` to the end of the segment.
```ts
{
path: ":lang?/categories";
}
```
You can have optional static segments, too:
```ts
{
path: "users/:userId/edit?";
}
```
## Splats
Also known as "catchall" and "star" segments. If a route path pattern ends with `/*` then it will match any characters following the `/`, including other `/` characters.
```ts
{
path: "files/*";
loader: async ({ params }) => {
params["*"]; // will contain the remaining URL after files/
};
}
```
You can destructure the `*`, you just have to assign it a new name. A common name is `splat`:
```tsx
const { "*": splat } = params;
```
---
Next: [Route Object](./route-object)
[outlet]: https://api.reactrouter.com/v7/functions/react-router.Outlet.html
+8
View File
@@ -0,0 +1,8 @@
---
title: Testing
order: 9
---
# Testing
You can use `createRoutesStub` in data and framework modes. Please refer to the [Testing Guide](../framework/testing).
+4
View File
@@ -0,0 +1,4 @@
---
title: Declarative Mode
order: 4
---
@@ -0,0 +1,43 @@
---
title: Installation
order: 1
---
# Installation
[MODES: declarative]
## Introduction
You can start with a React template from Vite and choose "React", otherwise bootstrap your application however you prefer.
```shellscript nonumber
npx create-vite@latest
```
Next install React Router from npm:
```shellscript nonumber
npm i react-router
```
Finally, render a `<BrowserRouter>` around your application:
```tsx lines=[3,9-11]
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./app";
const root = document.getElementById("root");
ReactDOM.createRoot(root).render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
```
---
Next: [Routing](./routing)
+133
View File
@@ -0,0 +1,133 @@
---
title: Navigating
order: 3
---
# Navigating
[MODES: declarative]
## Introduction
Users navigate your application with `<Link>`, `<NavLink>`, and `useNavigate`.
## NavLink
This component is for navigation links that need to render an active state.
```tsx
import { NavLink } from "react-router";
export function MyAppNav() {
return (
<nav>
<NavLink to="/" end>
Home
</NavLink>
<NavLink to="/trending" end>
Trending Concerts
</NavLink>
<NavLink to="/concerts">All Concerts</NavLink>
<NavLink to="/account">Account</NavLink>
</nav>
);
}
```
Whenever a `NavLink` is active, it will automatically have an `.active` class name for easy styling with CSS:
```css
a.active {
color: red;
}
```
It also has callback props on `className`, `style`, and `children` with the active state for inline styling or conditional rendering:
```tsx
// className
<NavLink
to="/messages"
className={({ isActive }) =>
isActive ? "text-red-500" : "text-black"
}
>
Messages
</NavLink>
```
```tsx
// style
<NavLink
to="/messages"
style={({ isActive }) => ({
color: isActive ? "red" : "black",
})}
>
Messages
</NavLink>
```
```tsx
// children
<NavLink to="/message">
{({ isActive }) => (
<span className={isActive ? "active" : ""}>
{isActive ? "👉" : ""} Tasks
</span>
)}
</NavLink>
```
## Link
Use `<Link>` when the link doesn't need active styling:
```tsx
import { Link } from "react-router";
export function LoggedOutMessage() {
return (
<p>
You've been logged out.{" "}
<Link to="/login">Login again</Link>
</p>
);
}
```
## useNavigate
This hook allows the programmer to navigate the user to a new page without the user interacting.
For normal navigation, it's best to use `Link` or `NavLink`. They provide a better default user experience like keyboard events, accessibility labeling, "open in new window", right click context menus, etc.
Reserve usage of `useNavigate` to situations where the user is _not_ interacting but you need to navigate, for example:
- After a form submission completes
- Logging them out after inactivity
- Timed UIs like quizzes, etc.
```tsx
import { useNavigate } from "react-router";
export function LoginPage() {
let navigate = useNavigate();
return (
<>
<MyHeader />
<MyLoginForm
onSuccess={() => {
navigate("/dashboard");
}}
/>
<MyFooter />
</>
);
}
```
---
Next: [Url values](./url-values)
+237
View File
@@ -0,0 +1,237 @@
---
title: Routing
order: 2
---
# Routing
[MODES: declarative]
## Configuring Routes
Routes are configured by rendering `<Routes>` and `<Route>` that couple URL segments to UI elements.
```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router";
import App from "./app";
const root = document.getElementById("root");
ReactDOM.createRoot(root).render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>,
);
```
Here's a larger sample config:
```tsx
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
</Route>
<Route path="concerts">
<Route index element={<ConcertsHome />} />
<Route path=":city" element={<City />} />
<Route path="trending" element={<Trending />} />
</Route>
</Routes>
```
## Nested Routes
Routes can be nested inside parent routes.
```tsx
<Routes>
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<Home />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
```
The path of the parent is automatically included in the child, so this config creates both `"/dashboard"` and `"/dashboard/settings"` URLs.
Child routes are rendered through the `<Outlet/>` in the parent route.
```tsx filename=app/dashboard.tsx
import { Outlet } from "react-router";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* will either be <Home/> or <Settings/> */}
<Outlet />
</div>
);
}
```
## Layout Routes
Routes _without_ a `path` create new nesting for their children, but they don't add any segments to the URL.
```tsx lines=[2,9]
<Routes>
<Route element={<MarketingLayout />}>
<Route index element={<MarketingHome />} />
<Route path="contact" element={<Contact />} />
</Route>
<Route path="projects">
<Route index element={<ProjectsHome />} />
<Route element={<ProjectsLayout />}>
<Route path=":pid" element={<Project />} />
<Route path=":pid/edit" element={<EditProject />} />
</Route>
</Route>
</Routes>
```
## Index Routes
Index routes render into their parent's `<Outlet/>` at their parent's URL (like a default child route). They are configured with the `index` prop:
```tsx lines=[4,8]
<Routes>
<Route path="/" element={<Root />}>
{/* renders into the outlet in <Root> at "/" */}
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />}>
{/* renders into the outlet in <Dashboard> at "/dashboard" */}
<Route index element={<DashboardHome />} />
<Route path="settings" element={<Settings />} />
</Route>
</Route>
</Routes>
```
Note that index routes can't have children. If you're expecting that behavior, you probably want a [layout route](#layout-routes).
## Route Prefixes
A `<Route path>` _without_ an `element` prop adds a path prefix to its child routes, without introducing a parent layout.
```tsx filename=app/routes.ts lines=[1]
<Route path="projects">
<Route index element={<ProjectsHome />} />
<Route element={<ProjectsLayout />}>
<Route path=":pid" element={<Project />} />
<Route path=":pid/edit" element={<EditProject />} />
</Route>
</Route>
```
## Dynamic Segments
If a path segment starts with `:` then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as `params` to other router APIs like `useParams`.
```tsx
<Route path="teams/:teamId" element={<Team />} />
```
```tsx filename=app/team.tsx
import { useParams } from "react-router";
export default function Team() {
let params = useParams();
// params.teamId
}
```
You can have multiple dynamic segments in one route path:
```tsx
<Route
path="/c/:categoryId/p/:productId"
element={<Product />}
/>
```
```tsx filename=app/category-product.tsx
import { useParams } from "react-router";
export default function CategoryProduct() {
let { categoryId, productId } = useParams();
// ...
}
```
You should ensure that all dynamic segments in a given path are unique. Otherwise, as the `params` object is populated - latter dynamic segment values will override earlier values.
## Optional Segments
You can make a route segment optional by adding a `?` to the end of the segment.
```tsx
<Route path=":lang?/categories" element={<Categories />} />
```
You can have optional static segments, too:
```tsx
<Route path="users/:userId/edit?" element={<User />} />
```
## Splats
Also known as "catchall" and "star" segments. If a route path pattern ends with `/*` then it will match any characters following the `/`, including other `/` characters.
```tsx
<Route path="files/*" element={<File />} />
```
```tsx
let params = useParams();
// params["*"] will contain the remaining URL after files/
let filePath = params["*"];
```
You can destructure the `*`, you just have to assign it a new name. A common name is `splat`:
```tsx
let { "*": splat } = useParams();
```
## Linking
Link to routes from your UI with `Link` and `NavLink`
```tsx
import { NavLink, Link } from "react-router";
function Header() {
return (
<nav>
{/* NavLink makes it easy to show active states */}
<NavLink
to="/"
className={({ isActive }) =>
isActive ? "active" : ""
}
>
Home
</NavLink>
<Link to="/concerts/salt-lake-city">Concerts</Link>
</nav>
);
}
```
---
Next: [Navigating](./navigating)
@@ -0,0 +1,65 @@
---
title: URL Values
---
# URL Values
[MODES: declarative]
## Route Params
Route params are the parsed values from a dynamic segment.
```tsx
<Route path="/concerts/:city" element={<City />} />
```
In this case, `:city` is the dynamic segment. The parsed value for that city will be available from `useParams`
```tsx
import { useParams } from "react-router";
function City() {
let { city } = useParams();
let data = useFakeDataLibrary(`/api/v2/cities/${city}`);
// ...
}
```
## URL Search Params
Search params are the values after a `?` in the URL. They are accessible from `useSearchParams`, which returns an instance of [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
```tsx
function SearchResults() {
let [searchParams] = useSearchParams();
return (
<div>
<p>
You searched for <i>{searchParams.get("q")}</i>
</p>
<FakeSearchResults />
</div>
);
}
```
## Location Object
React Router creates a custom `location` object with some useful information on it accessible with `useLocation`.
```tsx
function useAnalytics() {
let location = useLocation();
useEffect(() => {
sendFakeAnalytics(location.pathname);
}, [location]);
}
function useScrollRestoration() {
let location = useLocation();
useEffect(() => {
fakeRestoreScroll(location.key);
}, [location]);
}
```
+174
View File
@@ -0,0 +1,174 @@
---
title: Actions
order: 6
---
# Actions
[MODES: framework]
## Introduction
Data mutations are done through Route actions. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it.
Route actions defined with `action` are only called on the server while actions defined with `clientAction` are run in the browser.
## Client Actions
Client actions only run in the browser and take priority over a server action when both are defined.
```tsx filename=app/project.tsx
// route('/projects/:projectId', './project.tsx')
import type { Route } from "./+types/project";
import { Form } from "react-router";
import { someApi } from "./api";
export async function clientAction({
request,
}: Route.ClientActionArgs) {
let formData = await request.formData();
let title = formData.get("title");
let project = await someApi.updateProject({ title });
return project;
}
export default function Project({
actionData,
}: Route.ComponentProps) {
return (
<div>
<h1>Project</h1>
<Form method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
{actionData ? (
<p>{actionData.title} updated</p>
) : null}
</div>
);
}
```
## Server Actions
Server actions only run on the server and are removed from client bundles.
```tsx filename=app/project.tsx
// route('/projects/:projectId', './project.tsx')
import type { Route } from "./+types/project";
import { Form } from "react-router";
import { fakeDb } from "../db";
export async function action({
request,
}: Route.ActionArgs) {
let formData = await request.formData();
let title = formData.get("title");
let project = await fakeDb.updateProject({ title });
return project;
}
export default function Project({
actionData,
}: Route.ComponentProps) {
return (
<div>
<h1>Project</h1>
<Form method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
{actionData ? (
<p>{actionData.title} updated</p>
) : null}
</div>
);
}
```
## Calling Actions
Actions are called declaratively through `<Form>` and imperatively through `useSubmit` (or `<fetcher.Form>` and `fetcher.submit`) by referencing the route's path and a "post" method.
### Calling actions with a Form
```tsx
import { Form } from "react-router";
function SomeComponent() {
return (
<Form action="/projects/123" method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
);
}
```
This will cause a navigation and a new entry will be added to the browser history.
### Calling actions with useSubmit
You can submit form data to an action imperatively with `useSubmit`.
```tsx
import { useCallback } from "react";
import { useSubmit } from "react-router";
import { useFakeTimer } from "fake-lib";
function useQuizTimer() {
let submit = useSubmit();
let cb = useCallback(() => {
submit(
{ quizTimedOut: true },
{ action: "/end-quiz", method: "post" },
);
}, []);
let tenMinutes = 10 * 60 * 1000;
useFakeTimer(tenMinutes, cb);
}
```
This will cause a navigation and a new entry will be added to the browser history.
### Calling actions with a fetcher
Fetchers allow you to submit data to actions (and loaders) without causing a navigation (no new entries in the browser history).
```tsx
import { useFetcher } from "react-router";
function Task() {
let fetcher = useFetcher();
let busy = fetcher.state !== "idle";
return (
<fetcher.Form method="post" action="/update-task/123">
<input type="text" name="title" />
<button type="submit">
{busy ? "Saving..." : "Save"}
</button>
</fetcher.Form>
);
}
```
They also have the imperative `submit` method.
```tsx
fetcher.submit(
{ title: "New Title" },
{ action: "/update-task/123", method: "post" },
);
```
See the [Using Fetchers][fetchers] guide for more information.
---
Next: [Navigating](./navigating)
[fetchers]: ../../how-to/fetchers
+201
View File
@@ -0,0 +1,201 @@
---
title: Data Loading
order: 5
---
# Data Loading
[MODES: framework]
## Introduction
Data is provided to the route component from `loader` and `clientLoader`.
Loader data is automatically serialized from loaders and deserialized in components. In addition to primitive values like strings and numbers, loaders can return promises, maps, sets, dates and more.
The type for the `loaderData` prop is [automatically generated][type-safety].
<docs-info>We try to support the same set of [serializable types][serializable-types] that React permits server components to pass as props to client components. This future proofs your application for any eventual migration to [RSC][rsc].</docs-info>
## Client Data Loading
`clientLoader` is used to fetch data on the client. This is useful for pages or full projects that you'd prefer to fetch data from the browser only.
```tsx filename=app/product.tsx
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
export async function clientLoader({
params,
}: Route.ClientLoaderArgs) {
const res = await fetch(`/api/products/${params.pid}`);
const product = await res.json();
return product;
}
// HydrateFallback is rendered while the client loader is running
export function HydrateFallback() {
return <div>Loading...</div>;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
```
## Server Data Loading
When server rendering, `loader` is used for both initial page loads and client navigations. Client navigations call the loader through an automatic `fetch` by React Router from the browser to your server.
```tsx filename=app/product.tsx
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
import { fakeDb } from "../db";
export async function loader({ params }: Route.LoaderArgs) {
const product = await fakeDb.getProduct(params.pid);
return product;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
```
Note that the `loader` function is removed from client bundles so you can use server only APIs without worrying about them being included in the browser.
## Static Data Loading
When pre-rendering, loaders are used to fetch data during the production build.
```tsx filename=app/product.tsx
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
export async function loader({ params }: Route.LoaderArgs) {
let product = await getProductFromCSVFile(params.pid);
return product;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
```
The URLs to pre-render are specified in `react-router.config.ts`:
```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
async prerender() {
let products = await readProductsFromCSVFile();
return products.map(
(product) => `/products/${product.id}`,
);
},
} satisfies Config;
```
Note that when server rendering, any URLs that aren't pre-rendered will be server rendered as usual, allowing you to pre-render some data at a single route while still server rendering the rest.
## Using Both Loaders
`loader` and `clientLoader` can be used together. The `loader` will be used on the server for initial SSR (or pre-rendering) and the `clientLoader` will be used on subsequent client-side navigations.
```tsx filename=app/product.tsx
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types/product";
import { fakeDb } from "../db";
export async function loader({ params }: Route.LoaderArgs) {
return fakeDb.getProduct(params.pid);
}
export async function clientLoader({
serverLoader,
params,
}: Route.ClientLoaderArgs) {
const res = await fetch(`/api/products/${params.pid}`);
const serverData = await serverLoader();
return { ...serverData, ...(await res.json()) };
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
```
You can also force the client loader to run during hydration and before the page renders by setting the `hydrate` property on the function. In this situation you will want to render a `HydrateFallback` component to show a fallback UI while the client loader runs.
```tsx filename=app/product.tsx
export async function loader() {
/* ... */
}
export async function clientLoader() {
/* ... */
}
// force the client loader to run during hydration
clientLoader.hydrate = true as const; // `as const` for type inference
export function HydrateFallback() {
return <div>Loading...</div>;
}
export default function Product() {
/* ... */
}
```
---
Next: [Actions][actions]
See also:
- [Streaming with Suspense][streaming]
- [Client Data][client-data]
- [Using Fetchers][fetchers]
[type-safety]: ../../explanation/type-safety
[serializable-types]: https://react.dev/reference/rsc/use-client#serializable-types
[rsc]: ../../how-to/react-server-components
[actions]: ./actions
[streaming]: ../../how-to/suspense
[client-data]: ../../how-to/client-data
[fetchers]: ../../how-to/fetchers#loading-data
+100
View File
@@ -0,0 +1,100 @@
---
title: Deploying
order: 10
---
# Deploying
[MODES: framework]
## Introduction
React Router can be deployed two ways:
- Fullstack Hosting
- Static Hosting
The official [React Router templates](https://github.com/remix-run/react-router-templates) can help you bootstrap an application or be used as a reference for your own application.
When deploying to static hosting, you can deploy React Router the same as any other single page application with React.
## Templates
After running the `create-react-router` command, make sure to follow the instructions in the README.
### Node.js with Docker
```
npx create-react-router@latest --template remix-run/react-router-templates/default
```
- Server Rendering
- Tailwind CSS
The containerized application can be deployed to any platform that supports Docker, including:
- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway
### Node with Docker (Custom Server)
```
npx create-react-router@latest --template remix-run/react-router-templates/node-custom-server
```
- Server Rendering
- Tailwind CSS
- Custom express server for more control
The containerized application can be deployed to any platform that supports Docker, including:
- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway
### Node with Docker and Postgres
```
npx create-react-router@latest --template remix-run/react-router-templates/node-postgres
```
- Server Rendering
- Postgres Database with Drizzle
- Tailwind CSS
- Custom express server for more control
The containerized application can be deployed to any platform that supports Docker, including:
- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway
### Vercel
Vercel maintains their own template for React Router. Checkout the [Vercel Guide](https://vercel.com/templates/react-router/react-router-boilerplate) for more information.
### Cloudflare Workers
Cloudflare maintains their own template for React Router. Checkout the [Cloudflare Guide](https://developers.cloudflare.com/workers/framework-guides/web-apps/react-router/) for more information.
### Netlify
Netlify maintains their own template for React Router. Checkout the [Netlify Guide](https://docs.netlify.com/build/frameworks/framework-setup-guides/react-router/) for more information.
### EdgeOne Pages
EdgeOne Pages maintains their own template for React Router. Checkout the [EdgeOne Pages Guide](https://pages.edgeone.ai/document/framework-react-router) for more information.
### DeployHQ
DeployHQ maintains their own guide for deploying React Router v7 to your own server. Checkout the [DeployHQ Guide](https://www.deployhq.com/guides/deploy-react-router-from-github) for more information.
+4
View File
@@ -0,0 +1,4 @@
---
title: Framework Mode
order: 2
---
@@ -0,0 +1,41 @@
---
title: Installation
order: 1
---
# Installation
[MODES: framework]
## Introduction
Most projects start with a template. Let's use a basic template maintained by React Router:
```shellscript nonumber
npx create-react-router@latest my-react-router-app
```
Now change into the new directory and start the app
```shellscript nonumber
cd my-react-router-app
npm i
npm run dev
```
You can now open your browser to `http://localhost:5173`
You can [view the template on GitHub][default-template] to see how to manually set up your project.
We also have a number of [ready to deploy templates][react-router-templates] available for you to get started with:
```shellscript nonumber
npx create-react-router@latest --template remix-run/react-router-templates/<template-name>
```
---
Next: [Routing](./routing)
[default-template]: https://github.com/remix-run/react-router-templates/tree/main/default
[react-router-templates]: https://github.com/remix-run/react-router-templates
+182
View File
@@ -0,0 +1,182 @@
---
title: Navigating
order: 6
---
# Navigating
[MODES: framework]
## Introduction
Users navigate your application with `<Link>`, `<NavLink>`, `<Form>`, `redirect`, and `useNavigate`.
## NavLink
This component is for navigation links that need to render active and pending states.
```tsx
import { NavLink } from "react-router";
export function MyAppNav() {
return (
<nav>
<NavLink to="/" end>
Home
</NavLink>
<NavLink to="/trending" end>
Trending Concerts
</NavLink>
<NavLink to="/concerts">All Concerts</NavLink>
<NavLink to="/account">Account</NavLink>
</nav>
);
}
```
`NavLink` renders default class names for different states for easy styling with CSS:
```css
a.active {
color: red;
}
a.pending {
animate: pulse 1s infinite;
}
a.transitioning {
/* css transition is running */
}
```
It also has callback props on `className`, `style`, and `children` with the states for inline styling or conditional rendering:
```tsx
// className
<NavLink
to="/messages"
className={({ isActive, isPending, isTransitioning }) =>
[
isPending ? "pending" : "",
isActive ? "active" : "",
isTransitioning ? "transitioning" : "",
].join(" ")
}
>
Messages
</NavLink>
```
```tsx
// style
<NavLink
to="/messages"
style={({ isActive, isPending, isTransitioning }) => {
return {
fontWeight: isActive ? "bold" : "",
color: isPending ? "red" : "black",
viewTransitionName: isTransitioning ? "slide" : "",
};
}}
>
Messages
</NavLink>
```
```tsx
// children
<NavLink to="/tasks">
{({ isActive, isPending, isTransitioning }) => (
<span className={isActive ? "active" : ""}>Tasks</span>
)}
</NavLink>
```
## Link
Use `<Link>` when the link doesn't need active styling:
```tsx
import { Link } from "react-router";
export function LoggedOutMessage() {
return (
<p>
You've been logged out.{" "}
<Link to="/login">Login again</Link>
</p>
);
}
```
## Form
The form component can be used to navigate with `URLSearchParams` provided by the user.
```tsx
<Form action="/search">
<input type="text" name="q" />
</Form>
```
If the user enters "journey" into the input and submits it, they will navigate to:
```
/search?q=journey
```
Forms with `<Form method="post" />` will also navigate to the action prop but will submit the data as `FormData` instead of `URLSearchParams`. However, it is more common to `useFetcher()` to POST form data. See [Using Fetchers](../../how-to/fetchers).
## redirect
Inside of route loaders and actions, you can return a `redirect` to another URL.
```tsx
import { redirect } from "react-router";
export async function loader({ request }) {
let user = await getUser(request);
if (!user) {
return redirect("/login");
}
return { userName: user.name };
}
```
It is common to redirect to a new record after it has been created:
```tsx
import { redirect } from "react-router";
export async function action({ request }) {
let formData = await request.formData();
let project = await createProject(formData);
return redirect(`/projects/${project.id}`);
}
```
## useNavigate
This hook allows the programmer to navigate the user to a new page without the user interacting. Usage of this hook should be uncommon. It's recommended to use the other APIs in this guide when possible.
Reserve usage of `useNavigate` to situations where the user is _not_ interacting but you need to navigate, for example:
- Logging them out after inactivity
- Timed UIs like quizzes, etc.
```tsx
import { useNavigate } from "react-router";
export function useLogoutAfterInactivity() {
let navigate = useNavigate();
useFakeInactivityHook(() => {
navigate("/logout");
});
}
```
---
Next: [Pending UI](./pending-ui)
+142
View File
@@ -0,0 +1,142 @@
---
title: Pending UI
order: 7
---
# Pending UI
[MODES: framework]
## Introduction
When the user navigates to a new route, or submits data to an action, the UI should immediately respond to the user's actions with a pending or optimistic state. Application code is responsible for this.
## Global Pending Navigation
When the user navigates to a new url, the loaders for the next page are awaited before the next page renders. You can get the pending state from `useNavigation`.
```tsx
import { useNavigation } from "react-router";
export default function Root() {
const navigation = useNavigation();
const isNavigating = Boolean(navigation.location);
return (
<html>
<body>
{isNavigating && <GlobalSpinner />}
<Outlet />
</body>
</html>
);
}
```
## Local Pending Navigation
Pending indicators can also be localized to the link. NavLink's children, className, and style props can be functions that receive the pending state.
```tsx
import { NavLink } from "react-router";
function Navbar() {
return (
<nav>
<NavLink to="/home">
{({ isPending }) => (
<span>Home {isPending && <Spinner />}</span>
)}
</NavLink>
<NavLink
to="/about"
style={({ isPending }) => ({
color: isPending ? "gray" : "black",
})}
>
About
</NavLink>
</nav>
);
}
```
## Pending Form Submission
When a form is submitted, the UI should immediately respond to the user's actions with a pending state. This is easiest to do with a [fetcher][use_fetcher] form because it has its own independent state (whereas normal forms cause a global navigation).
```tsx filename=app/project.tsx lines=[10-12]
import { useFetcher } from "react-router";
function NewProjectForm() {
const fetcher = useFetcher();
return (
<fetcher.Form method="post">
<input type="text" name="title" />
<button type="submit">
{fetcher.state !== "idle"
? "Submitting..."
: "Submit"}
</button>
</fetcher.Form>
);
}
```
For non-fetcher form submissions, pending states are available on `useNavigation`.
```tsx filename=app/projects/new.tsx
import { useNavigation, Form } from "react-router";
function NewProjectForm() {
const navigation = useNavigation();
return (
<Form method="post" action="/projects/new">
<input type="text" name="title" />
<button type="submit">
{navigation.formAction === "/projects/new"
? "Submitting..."
: "Submit"}
</button>
</Form>
);
}
```
## Optimistic UI
When the future state of the UI is known by the form submission data, an optimistic UI can be implemented for instant UX.
```tsx filename=app/project.tsx lines=[4-7]
function Task({ task }) {
const fetcher = useFetcher();
let isComplete = task.status === "complete";
if (fetcher.formData) {
isComplete =
fetcher.formData.get("status") === "complete";
}
return (
<div>
<div>{task.title}</div>
<fetcher.Form method="post">
<button
name="status"
value={isComplete ? "incomplete" : "complete"}
>
{isComplete ? "Mark Incomplete" : "Mark Complete"}
</button>
</fetcher.Form>
</div>
);
}
```
---
Next: [Testing](./testing)
[use_fetcher]: https://api.reactrouter.com/v7/functions/react-router.useFetcher.html
+59
View File
@@ -0,0 +1,59 @@
---
title: Rendering Strategies
order: 4
---
# Rendering Strategies
[MODES: framework]
## Introduction
There are three rendering strategies in React Router:
- Client Side Rendering
- Server Side Rendering
- Static Pre-rendering
## Client Side Rendering
Routes are always client side rendered as the user navigates around the app. If you're looking to build a Single Page App, disable server rendering:
```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: false,
} satisfies Config;
```
## Server Side Rendering
```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
} satisfies Config;
```
Server side rendering requires a deployment that supports it. Though it's a global setting, individual routes can still be statically pre-rendered. Routes can also use client data loading with `clientLoader` to avoid server rendering/fetching for their portion of the UI.
## Static Pre-rendering
```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
// return a list of URLs to prerender at build time
async prerender() {
return ["/", "/about", "/contact"];
},
} satisfies Config;
```
Pre-rendering is a build-time operation that generates static HTML and client navigation data payloads for a list of URLs. This is useful for SEO and performance, especially for deployments without server rendering. When pre-rendering, route module loaders are used to fetch data at build time.
---
Next: [Data Loading](./data-loading)
+527
View File
@@ -0,0 +1,527 @@
---
title: Route Module
order: 3
---
# Route Module
[MODES: framework]
## Introduction
The files referenced in `routes.ts` are called Route Modules.
```tsx filename=app/routes.ts
route("teams/:teamId", "./team.tsx"),
// route module ^^^^^^^^
```
Route modules are the foundation of React Router's framework features, they define:
- automatic code-splitting
- data loading
- actions
- revalidation
- error boundaries
- and more
This guide is a quick overview of every route module feature. The rest of the getting started guides will cover these features in more detail.
## Component (`default`)
The `default` export in a route module defines the component that will render when the route matches.
```tsx filename=app/routes/my-route.tsx
export default function MyRouteComponent() {
return (
<div>
<h1>Look ma!</h1>
<p>
I'm still using React Router after like 10 years.
</p>
</div>
);
}
```
### Props passed to the Component
When the component is rendered, it is provided the props defined in `Route.ComponentProps` that React Router will automatically generate for you. These props include:
1. `loaderData`: The data returned from the `loader` function in this route module
2. `actionData`: The data returned from the `action` function in this route module
3. `params`: An object containing the route parameters (if any).
4. `matches`: An array of all the matches in the current route tree.
You can use these props in place of hooks like `useLoaderData` or `useParams`. This may be preferable because they will be automatically typed correctly for the route.
### Using props
```tsx filename=app/routes/my-route-with-default-params.tsx
import type { Route } from "./+types/route-name";
export default function MyRouteComponent({
loaderData,
actionData,
params,
matches,
}: Route.ComponentProps) {
return (
<div>
<h1>Welcome to My Route with Props!</h1>
<p>Loader Data: {JSON.stringify(loaderData)}</p>
<p>Action Data: {JSON.stringify(actionData)}</p>
<p>Route Parameters: {JSON.stringify(params)}</p>
<p>Matched Routes: {JSON.stringify(matches)}</p>
</div>
);
}
```
## `middleware`
Route [middleware][middleware] runs sequentially on the server before and after document and
data requests. This gives you a singular place to do things like logging,
authentication, and post-processing of responses. The `next` function continues down the chain, and on the leaf route the `next` function executes the loaders/actions for the navigation.
Here's an example middleware to log requests on the server:
```tsx filename=root.tsx
async function loggingMiddleware(
{ request, context },
next,
) {
console.log(
`${new Date().toISOString()} ${request.method} ${request.url}`,
);
const start = performance.now();
const response = await next();
const duration = performance.now() - start;
console.log(
`${new Date().toISOString()} Response ${response.status} (${duration}ms)`,
);
return response;
}
export const middleware = [loggingMiddleware];
```
Here's an example middleware to check for logged in users and set the user in
`context` you can then access from loaders:
```tsx filename=routes/_auth.tsx
async function authMiddleware({ request, context }) {
const session = await getSession(request);
const userId = session.get("userId");
if (!userId) {
throw redirect("/login");
}
const user = await getUserById(userId);
context.set(userContext, user);
}
export const middleware = [authMiddleware];
```
<docs-warning>Please make sure you understand [when middleware runs][when-middleware-runs] to make sure your application will behave the way you intend when adding middleware to your routes.</docs-warning>
See also:
- [`middleware` params][middleware-params]
- [Middleware][middleware]
## `clientMiddleware`
This is the client-side equivalent of `middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server.
Here's an example middleware to log requests on the client:
```tsx filename=root.tsx
async function loggingMiddleware(
{ request, context },
next,
) {
console.log(
`${new Date().toISOString()} ${request.method} ${request.url}`,
);
const start = performance.now();
await next(); // 👈 No Response returned
const duration = performance.now() - start;
console.log(
`${new Date().toISOString()} (${duration}ms)`,
);
// ✅ No need to return anything
}
export const clientMiddleware = [loggingMiddleware];
```
See also:
- [Middleware][middleware]
- [Client Data][client-data]
## `loader`
Route loaders provide data to route components before they are rendered. They are only called on the server when server rendering or during the build with pre-rendering.
```tsx
export async function loader() {
return { message: "Hello, world!" };
}
export default function MyRoute({ loaderData }) {
return <h1>{loaderData.message}</h1>;
}
```
See also:
- [`loader` params][loader-params]
## `clientLoader`
Called only in the browser, route client loaders provide data to route components in addition to, or in place of, route loaders.
```tsx
export async function clientLoader({ serverLoader }) {
// call the server loader
const serverData = await serverLoader();
// And/or fetch data on the client
const data = getDataFromClient();
// Return the data to expose through useLoaderData()
return data;
}
```
Client loaders can participate in initial page load hydration of server rendered pages by setting the `hydrate` property on the function:
```tsx
export async function clientLoader() {
// ...
}
clientLoader.hydrate = true as const;
```
<docs-info>
By using `as const`, TypeScript will infer that the type for `clientLoader.hydrate` is `true` instead of `boolean`.
That way, React Router can derive types for `loaderData` based on the value of `clientLoader.hydrate`.
</docs-info>
See also:
- [`clientLoader` params][client-loader-params]
- [Client Data][client-data]
## `action`
Route actions allow server-side data mutations with automatic revalidation of all loader data on the page when called from `<Form>`, `useFetcher`, and `useSubmit`.
```tsx
// route("/list", "./list.tsx")
import { Form } from "react-router";
import { TodoList } from "~/components/TodoList";
// this data will be loaded after the action completes...
export async function loader() {
const items = await fakeDb.getItems();
return { items };
}
// ...so that the list here is updated automatically
export default function Items({ loaderData }) {
return (
<div>
<List items={loaderData.items} />
<Form method="post" navigate={false} action="/list">
<input type="text" name="title" />
<button type="submit">Create Todo</button>
</Form>
</div>
);
}
export async function action({ request }) {
const data = await request.formData();
const todo = await fakeDb.addItem({
title: data.get("title"),
});
return { ok: true };
}
```
See also:
- [`action` params][action-params]
## `clientAction`
Like route actions but only called in the browser.
```tsx
export async function clientAction({ serverAction }) {
fakeInvalidateClientSideCache();
// can still call the server action if needed
const data = await serverAction();
return data;
}
```
See also:
- [`clientAction` params][client-action-params]
- [Client Data][client-data]
## `ErrorBoundary`
When other route module APIs throw, the route module `ErrorBoundary` will render instead of the route component.
```tsx
import {
isRouteErrorResponse,
useRouteError,
} from "react-router";
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</div>
);
} else if (error instanceof Error) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
} else {
return <h1>Unknown Error</h1>;
}
}
```
See also:
- [`useRouteError`][use-route-error]
- [`isRouteErrorResponse`][is-route-error-response]
## `HydrateFallback`
On initial page load, the route component renders only after the client loader is finished. If exported, a `HydrateFallback` can render immediately in place of the route component.
```tsx filename=routes/client-only-route.tsx
export async function clientLoader() {
const data = await fakeLoadLocalGameData();
return data;
}
export function HydrateFallback() {
return <p>Loading Game...</p>;
}
export default function Component({ loaderData }) {
return <Game data={loaderData} />;
}
```
## `headers`
The route `headers` function defines the HTTP headers to be sent with the response when server rendering.
```tsx
export function headers() {
return {
"X-Stretchy-Pants": "its for fun",
"Cache-Control": "max-age=300, s-maxage=3600",
};
}
```
See also:
- [`Headers`][headers]
## `handle`
Route handle allows apps to add anything to a route match in `useMatches` to create abstractions (like breadcrumbs, etc.).
```tsx
export const handle = {
its: "all yours",
};
```
See also:
- [`useMatches`][use-matches]
## `links`
Route links define [`<link>` element][link-element]s to be rendered in the document `<head>`.
```tsx
export function links() {
return [
{
rel: "icon",
href: "/favicon.png",
type: "image/png",
},
{
rel: "stylesheet",
href: "https://example.com/some/styles.css",
},
{
rel: "preload",
href: "/images/banner.jpg",
as: "image",
},
];
}
```
All routes links will be aggregated and rendered through the `<Links />` component, usually rendered in your app root:
```tsx
import { Links } from "react-router";
export default function Root() {
return (
<html>
<head>
<Links />
</head>
<body />
</html>
);
}
```
See also:
- [Styling][styling]
## `meta`
Route meta defines [meta tags][meta-element] to be rendered in the `<Meta />` component, usually placed in the `<head>`.
<docs-warning>
Since React 19, [using the built-in `<meta>` element](https://react.dev/reference/react-dom/components/meta) is recommended over the use of the route module's `meta` export.
Here is an example of how to use it and the `<title>` element:
```tsx
export default function MyRoute() {
return (
<div>
<title>Very cool app</title>
<meta property="og:title" content="Very cool app" />
<meta
name="description"
content="This app is the best"
/>
{/* The rest of your route content... */}
</div>
);
}
```
</docs-warning>
```tsx filename=app/product.tsx
export function meta() {
return [
{ title: "Very cool app" },
{
property: "og:title",
content: "Very cool app",
},
{
name: "description",
content: "This app is the best",
},
];
}
```
```tsx filename=app/root.tsx
import { Meta } from "react-router";
export default function Root() {
return (
<html>
<head>
<Meta />
</head>
<body />
</html>
);
}
```
The meta of the last matching route is used, allowing you to override parent routes' meta. It's important to note that the entire meta descriptor array is replaced, not merged. This gives you the flexibility to build your own meta composition logic across pages at different levels.
**See also**
- [`meta` params][meta-params]
- [`meta` function return types][meta-function]
## `shouldRevalidate`
In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than they would in Data Mode.
Defining this function allows you to opt out of revalidation for a route loader for navigations and form submissions.
```tsx
import type { ShouldRevalidateFunctionArgs } from "react-router";
export function shouldRevalidate(
arg: ShouldRevalidateFunctionArgs,
) {
return true;
}
```
When using [SPA Mode][spa-mode], there are no server loaders to call on navigations, so `shouldRevalidate` behaves the same as it does in [Data Mode][data-mode-should-revalidate].
[`ShouldRevalidateFunctionArgs` Reference Documentation ↗](https://api.reactrouter.com/v7/interfaces/react-router.ShouldRevalidateFunctionArgs.html)
---
Next: [Rendering Strategies](./rendering)
[middleware-params]: https://api.reactrouter.com/v7/types/react-router.MiddlewareFunction.html
[middleware]: ../../how-to/middleware
[when-middleware-runs]: ../../how-to/middleware#when-middleware-runs
[loader-params]: https://api.reactrouter.com/v7/interfaces/react-router.LoaderFunctionArgs
[client-loader-params]: https://api.reactrouter.com/v7/types/react-router.ClientLoaderFunctionArgs
[action-params]: https://api.reactrouter.com/v7/interfaces/react-router.ActionFunctionArgs
[client-action-params]: https://api.reactrouter.com/v7/types/react-router.ClientActionFunctionArgs
[use-route-error]: ../../api/hooks/useRouteError
[is-route-error-response]: ../../api/utils/isRouteErrorResponse
[headers]: https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
[use-matches]: ../../api/hooks/useMatches
[link-element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
[meta-element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
[meta-params]: https://api.reactrouter.com/v7/interfaces/react-router.MetaArgs
[meta-function]: https://api.reactrouter.com/v7/types/react-router.MetaDescriptor.html
[data-mode-should-revalidate]: ../data/route-object#shouldrevalidate
[spa-mode]: ../../how-to/spa
[client-data]: ../../how-to/client-data
[styling]: ../../explanation/styling
+362
View File
@@ -0,0 +1,362 @@
---
title: Routing
order: 2
---
# Routing
[MODES: framework]
## Configuring Routes
Routes are configured in `app/routes.ts`. Each route has two required parts: a URL pattern to match the URL, and a file path to the route module that defines its behavior.
```ts filename=app/routes.ts
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
route("some/path", "./some/file.tsx"),
// pattern ^ ^ module file
] satisfies RouteConfig;
```
Here is a larger sample route config:
```ts filename=app/routes.ts
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"),
route("trending", "./concerts/trending.tsx"),
]),
] satisfies RouteConfig;
```
If you prefer to define your routes via file naming conventions rather than configuration, the `@react-router/fs-routes` package provides a [file system routing convention][file-route-conventions]. You can even combine different routing conventions if you like:
```ts filename=app/routes.ts
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default [
route("/", "./home.tsx"),
...(await flatRoutes()),
] satisfies RouteConfig;
```
## Route Modules
The files referenced in `routes.ts` define each route's behavior:
```tsx filename=app/routes.ts
route("teams/:teamId", "./team.tsx"),
// route module ^^^^^^^^
```
Here's a sample route module:
```tsx filename=app/team.tsx
// provides type safety/inference
import type { Route } from "./+types/team";
// provides `loaderData` to the component
export async function loader({ params }: Route.LoaderArgs) {
let team = await fetchTeam(params.teamId);
return { name: team.name };
}
// renders after the loader is done
export default function Component({
loaderData,
}: Route.ComponentProps) {
return <h1>{loaderData.name}</h1>;
}
```
Route modules have more features like actions, headers, and error boundaries, but they will be covered in the next guide: [Route Modules](./route-module)
## Nested Routes
Routes can be nested inside parent routes.
```ts filename=app/routes.ts
import {
type RouteConfig,
route,
index,
} from "@react-router/dev/routes";
export default [
// parent route
route("dashboard", "./dashboard.tsx", [
// child routes
index("./home.tsx"),
route("settings", "./settings.tsx"),
]),
] satisfies RouteConfig;
```
The path of the parent is automatically included in the child, so this config creates both `"/dashboard"` and `"/dashboard/settings"` URLs.
Child routes are rendered through the `<Outlet/>` in the parent route.
```tsx filename=app/dashboard.tsx
import { Outlet } from "react-router";
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* will either be home.tsx or settings.tsx */}
<Outlet />
</div>
);
}
```
## Root Route
Every route in `routes.ts` is nested inside the special `app/root.tsx` module.
## Layout Routes
Using `layout`, layout routes create new nesting for their children, but they don't add any segments to the URL. It's like the root route but they can be added at any level.
```tsx filename=app/routes.ts lines=[10,16]
import {
type RouteConfig,
route,
layout,
index,
prefix,
} from "@react-router/dev/routes";
export default [
layout("./marketing/layout.tsx", [
index("./marketing/home.tsx"),
route("contact", "./marketing/contact.tsx"),
]),
...prefix("projects", [
index("./projects/home.tsx"),
layout("./projects/project-layout.tsx", [
route(":pid", "./projects/project.tsx"),
route(":pid/edit", "./projects/edit-project.tsx"),
]),
]),
] satisfies RouteConfig;
```
Note that:
- `home.tsx` and `contact.tsx` will be rendered into the `marketing/layout.tsx` outlet without creating any new URL paths
- `project.tsx` and `edit-project.tsx` will be rendered into the `projects/project-layout.tsx` outlet at `/projects/:pid` and `/projects/:pid/edit` while `projects/home.tsx` will not.
## Index Routes
```ts
index(componentFile),
```
Index routes render into their parent's [Outlet][outlet] at their parent's URL (like a default child route).
```ts filename=app/routes.ts
import {
type RouteConfig,
route,
index,
} from "@react-router/dev/routes";
export default [
// renders into the root.tsx Outlet at /
index("./home.tsx"),
route("dashboard", "./dashboard.tsx", [
// renders into the dashboard.tsx Outlet at /dashboard
index("./dashboard-home.tsx"),
route("settings", "./dashboard-settings.tsx"),
]),
] satisfies RouteConfig;
```
Note that index routes can't have children.
## Route Prefixes
Using `prefix`, you can add a path prefix to a set of routes without needing to introduce a parent route.
```tsx filename=app/routes.ts lines=[14]
import {
type RouteConfig,
route,
layout,
index,
prefix,
} from "@react-router/dev/routes";
export default [
layout("./marketing/layout.tsx", [
index("./marketing/home.tsx"),
route("contact", "./marketing/contact.tsx"),
]),
...prefix("projects", [
index("./projects/home.tsx"),
layout("./projects/project-layout.tsx", [
route(":pid", "./projects/project.tsx"),
route(":pid/edit", "./projects/edit-project.tsx"),
]),
]),
] satisfies RouteConfig;
```
Note that this does not introduce a new route into the route tree. Instead, it merely modifies the paths of its children.
For example, these two sets of routes are equivalent:
```ts filename=app/routes.ts
// This usage of `prefix`...
prefix("parent", [
route("child1", "./child1.tsx"),
route("child2", "./child2.tsx"),
])
// ...is equivalent to this:
[
route("parent/child1", "./child1.tsx"),
route("parent/child2", "./child2.tsx"),
]
```
## Dynamic Segments
If a path segment starts with `:` then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as `params` to other router APIs.
```ts filename=app/routes.ts
route("teams/:teamId", "./team.tsx"),
```
```tsx filename=app/team.tsx
import type { Route } from "./+types/team";
export async function loader({ params }: Route.LoaderArgs) {
// ^? { teamId: string }
}
export default function Component({
params,
}: Route.ComponentProps) {
params.teamId;
// ^ string
}
```
You can have multiple dynamic segments in one route path:
```ts filename=app/routes.ts
route("c/:categoryId/p/:productId", "./product.tsx"),
```
```tsx filename=app/product.tsx
import type { Route } from "./+types/product";
async function loader({ params }: LoaderArgs) {
// ^? { categoryId: string; productId: string }
}
```
## Optional Segments
You can make a route segment optional by adding a `?` to the end of the segment.
```ts filename=app/routes.ts
route(":lang?/categories", "./categories.tsx"),
```
You can have optional static segments, too:
```ts filename=app/routes.ts
route("users/:userId/edit?", "./user.tsx");
```
## Splats
Also known as "catchall" and "star" segments. If a route path pattern ends with `/*` then it will match any characters following the `/`, including other `/` characters.
```ts filename=app/routes.ts
route("files/*", "./files.tsx"),
```
```tsx filename=app/files.tsx
export async function loader({ params }: Route.LoaderArgs) {
// params["*"] will contain the remaining URL after files/
}
```
You can destructure the `*`, you just have to assign it a new name. A common name is `splat`:
```tsx
const { "*": splat } = params;
```
You can also use a splat to catch requests that don't match any route:
```ts filename=app/routes.ts
route("*", "./catchall.tsx"); // catchall route,
```
```tsx filename=app/catchall.tsx
export function loader() {
throw new Response("Page not found", { status: 404 });
}
```
## Component Routes
You can also use components that match the URL to elements anywhere in the component tree:
```tsx
import { Routes, Route } from "react-router";
function Wizard() {
return (
<div>
<h1>Some Wizard with Steps</h1>
<Routes>
<Route index element={<StepOne />} />
<Route path="step-2" element={<StepTwo />} />
<Route path="step-3" element={<StepThree />} />
</Routes>
</div>
);
}
```
Note that these routes do not participate in data loading, actions, code splitting, or any other route module features, so their use cases are more limited than those of the route module.
---
Next: [Route Module](./route-module)
[file-route-conventions]: ../../how-to/file-route-conventions
[outlet]: https://api.reactrouter.com/v7/functions/react-router.Outlet.html
+133
View File
@@ -0,0 +1,133 @@
---
title: Testing
order: 9
---
# Testing
[MODES: framework, data]
## Introduction
When components use things like `useLoaderData`, `<Link>`, etc, they are required to be rendered in context of a React Router app. The `createRoutesStub` function creates that context to test components in isolation.
Consider a login form component that relies on `useActionData`
```tsx
import { useActionData } from "react-router";
export function LoginForm() {
const actionData = useActionData();
const errors = actionData?.errors;
return (
<Form method="post">
<label>
<input type="text" name="username" />
{errors?.username && <div>{errors.username}</div>}
</label>
<label>
<input type="password" name="password" />
{errors?.password && <div>{errors.password}</div>}
</label>
<button type="submit">Login</button>
</Form>
);
}
```
We can test this component with `createRoutesStub`. It takes an array of objects that resemble route modules with loaders, actions, and components.
```tsx
import { createRoutesStub } from "react-router";
import {
render,
screen,
waitFor,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";
test("LoginForm renders error messages", async () => {
const USER_MESSAGE = "Username is required";
const PASSWORD_MESSAGE = "Password is required";
const Stub = createRoutesStub([
{
path: "/login",
Component: LoginForm,
action() {
return {
errors: {
username: USER_MESSAGE,
password: PASSWORD_MESSAGE,
},
};
},
},
]);
// render the app stub at "/login"
render(<Stub initialEntries={["/login"]} />);
// simulate interactions
userEvent.click(screen.getByText("Login"));
await waitFor(() => screen.findByText(USER_MESSAGE));
await waitFor(() => screen.findByText(PASSWORD_MESSAGE));
});
```
## Using with Framework Mode Types
It's important to note that `createRoutesStub` is designed for _unit_ testing of reusable components in your application that rely on contextual router information (i.e., `loaderData`, `actionData`, `matches`). These components usually obtain this information via the hooks (`useLoaderData`, `useActionData`, `useMatches`) or via props passed down from the ancestor route component. We **strongly** recommend limiting your usage of `createRoutesStub` to unit testing of these types of reusable components.
`createRoutesStub` is _not designed_ for (and is arguably incompatible with) direct testing of Route components using the [`Route.\*`](../../explanation/type-safety) types available in Framework Mode. This is because the `Route.*` types are derived from your actual application - including the real `loader`/`action` functions as well as the structure of your route tree structure (which defines the `matches` type). When you use `createRoutesStub`, you are providing stubbed values for `loaderData`, `actionData`, and even your `matches` based on the route tree you pass to `createRoutesStub`. Therefore, the types won't align with the `Route.*` types and you'll get type issues trying to use a route component in a route stub.
```tsx filename=routes/login.tsx
export default function Login({
actionData,
}: Route.ComponentProps) {
return <Form method="post">...</Form>;
}
```
```tsx filename=routes/login.test.tsx
import LoginRoute from "./login";
test("LoginRoute renders error messages", async () => {
const Stub = createRoutesStub([
{
path: "/login",
Component: LoginRoute,
// ^ ❌ Types of property 'matches' are incompatible.
action() {
/*...*/
},
},
]);
// ...
});
```
These type errors are generally accurate if you try to setup your tests like this. As long as your stubbed `loader`/`action` functions match your real implementations, then the types for `loaderData`/`actionData` will be correct, but if they differ your types will be lying to you.
`matches` is more complicated since you don't usually stub out all of the ancestor routes. In this example, there is no `root` route so `matches` will only contain your test route, while it will contain the root route and any other ancestors at runtime. There's no great way to automatically align the typegen types with the runtime types in your test.
Therefore, if you need to test Route level components, we recommend you do that via an Integration/E2E test (Playwright, Cypress, etc.) against a running application because you're venturing out of unit testing territory when testing your route as a whole.
If you _need_ to write a unit test against the route, you can add a `@ts-expect-error` comment in your test to silence the TypeScript error:
```tsx
const Stub = createRoutesStub([
{
path: "/login",
// @ts-expect-error: `matches` won't align between test code and app code
Component: LoginRoute,
action() {
/*...*/
},
},
]);
```
+4
View File
@@ -0,0 +1,4 @@
---
title: Getting Started
order: 1
---
+201
View File
@@ -0,0 +1,201 @@
---
title: Picking a Mode
order: 1
---
# Picking a Mode
React Router is a multi-strategy router for React. There are three primary ways, or "modes", to use it in your app. Across the docs you'll see these icons indicating which mode the content is relevant to:
[MODES: framework, data, declarative]
<p></p>
The features available in each mode are additive, so moving from Declarative to Data to Framework simply adds more features at the cost of architectural control. So pick your mode based on how much control or how much help you want from React Router.
The mode depends on which "top level" router API you're using:
## Declarative
Declarative mode enables basic routing features like matching URLs to components, navigating around the app, and providing active states with APIs like `<Link>`, `useNavigate`, and `useLocation`.
```tsx
import { BrowserRouter } from "react-router";
ReactDOM.createRoot(root).render(
<BrowserRouter>
<App />
</BrowserRouter>,
);
```
## Data
By moving route configuration outside of React rendering, Data Mode adds data loading, actions, pending states and more with APIs like `loader`, `action`, and `useFetcher`.
```tsx
import {
createBrowserRouter,
RouterProvider,
} from "react-router";
let router = createBrowserRouter([
{
path: "/",
Component: Root,
loader: loadRootData,
},
]);
ReactDOM.createRoot(root).render(
<RouterProvider router={router} />,
);
```
## Framework
Framework Mode wraps Data Mode with a Vite plugin to add the full React Router experience with:
- type-safe `href`
- type-safe Route Module API
- intelligent code splitting
- SPA, SSR, and static rendering strategies
- and more
```ts filename=routes.ts
import { index, route } from "@react-router/dev/routes";
export default [
index("./home.tsx"),
route("products/:pid", "./product.tsx"),
];
```
You'll then have access to the Route Module API with type-safe params, loaderData, code splitting, SPA/SSR/SSG strategies, and more.
```ts filename=product.tsx
import { Route } from "./+types/product.tsx";
export async function loader({ params }: Route.LoaderArgs) {
let product = await getProduct(params.pid);
return { product };
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
return <div>{loaderData.product.name}</div>;
}
```
## Decision Advice
Every mode supports any architecture and deployment target, so the question isn't really about if you want SSR, SPA, etc. It's about how much you want to do yourself.
**Use Framework Mode if you:**
- are too new to have an opinion
- are considering Next.js, Solid Start, SvelteKit, Astro, TanStack Start, etc. and want to compare
- just want to build something with React
- might want to server render, might not
- are coming from Remix (React Router v7 is the "next version" after Remix v2)
- are migrating from Next.js
[→ Get Started with Framework Mode](./framework/installation).
**Use Data Mode if you:**
- want data features but also want to have control over bundling, data, and server abstractions
- started a data router in v6.4 and are happy with it
[→ Get Started with Data Mode](./data/custom).
**Use Declarative Mode if you:**
- want to use React Router as simply as possible
- are coming from v6 and are happy with the `<BrowserRouter>`
- have a data layer that either skips pending states (like local first, background data replication/sync) or has its own abstractions for them
- are coming from Create React App (you may want to consider framework mode though)
[→ Get Started with Declarative Mode](./declarative/installation).
## API + Mode Availability Table
This is mostly for the LLMs, but knock yourself out:
| API | Framework | Data | Declarative |
| ------------------------------ | --------- | ---- | ----------- |
| Await | ✅ | ✅ | |
| Form | ✅ | ✅ |
| Link | ✅ | ✅ | ✅ |
| `<Link discover>` | ✅ | | |
| `<Link prefetch>` | ✅ | | |
| `<Link preventScrollReset>` | ✅ | ✅ | |
| Links | ✅ | | |
| Meta | ✅ | | |
| NavLink | ✅ | ✅ | ✅ |
| `<NavLink discover>` | ✅ | | |
| `<NavLink prefetch>` | ✅ | | |
| `<NavLink preventScrollReset>` | ✅ | ✅ | |
| NavLink `isPending` | ✅ | ✅ | |
| Navigate | ✅ | ✅ | ✅ |
| Outlet | ✅ | ✅ | ✅ |
| PrefetchPageLinks | ✅ | | |
| Route | ✅ | ✅ | ✅ |
| Routes | ✅ | ✅ | ✅ |
| Scripts | ✅ | | |
| ScrollRestoration | ✅ | ✅ | |
| ServerRouter | ✅ | | |
| usePrompt | ✅ | ✅ | |
| useActionData | ✅ | ✅ | |
| useAsyncError | ✅ | ✅ | |
| useAsyncValue | ✅ | ✅ | |
| useBeforeUnload | ✅ | ✅ | ✅ |
| useBlocker | ✅ | ✅ | |
| useFetcher | ✅ | ✅ | |
| useFetchers | ✅ | ✅ | |
| useFormAction | ✅ | ✅ | |
| useHref | ✅ | ✅ | ✅ |
| useInRouterContext | ✅ | ✅ | ✅ |
| useLinkClickHandler | ✅ | ✅ | ✅ |
| useLoaderData | ✅ | ✅ | |
| useLocation | ✅ | ✅ | ✅ |
| useMatch | ✅ | ✅ | ✅ |
| useMatches | ✅ | ✅ | |
| useNavigate | ✅ | ✅ | ✅ |
| useNavigation | ✅ | ✅ | |
| useNavigationType | ✅ | ✅ | ✅ |
| useOutlet | ✅ | ✅ | ✅ |
| useOutletContext | ✅ | ✅ | ✅ |
| useParams | ✅ | ✅ | ✅ |
| useResolvedPath | ✅ | ✅ | ✅ |
| useRevalidator | ✅ | ✅ | |
| useRouteError | ✅ | ✅ | |
| useRouteLoaderData | ✅ | ✅ | |
| useRoutes | ✅ | ✅ | ✅ |
| useSearchParams | ✅ | ✅ | ✅ |
| useSubmit | ✅ | ✅ | |
| useViewTransitionState | ✅ | ✅ | |
| isCookieFunction | ✅ | ✅ | |
| isSessionFunction | ✅ | ✅ | |
| createCookie | ✅ | ✅ | |
| createCookieSessionStorage | ✅ | ✅ | |
| createMemorySessionStorage | ✅ | ✅ | |
| createPath | ✅ | ✅ | ✅ |
| createRoutesFromElements | | ✅ | |
| createRoutesStub | ✅ | ✅ | |
| createSearchParams | ✅ | ✅ | ✅ |
| data | ✅ | ✅ | |
| generatePath | ✅ | ✅ | ✅ |
| href | ✅ | | |
| isCookie | ✅ | ✅ | |
| isRouteErrorResponse | ✅ | ✅ | |
| isSession | ✅ | ✅ | |
| matchPath | ✅ | ✅ | ✅ |
| matchRoutes | ✅ | ✅ | ✅ |
| parsePath | ✅ | ✅ | ✅ |
| redirect | ✅ | ✅ | |
| redirectDocument | ✅ | ✅ | |
| renderMatches | ✅ | ✅ | ✅ |
| replace | ✅ | ✅ | |
| resolvePath | ✅ | ✅ | ✅ |