Wβ
All docs
π
Sign Up/Sign In
reactrouter.com/start/framework/ (+2)
Public Link
Apr 16, 2025, 3:11:27 PM - complete - 177.1 kB
Apr 16, 2025, 3:11:27 PM - complete - 177.1 kB
Apr 16, 2025, 3:09:28 PM - complete - 39.3 kB
Apr 16, 2025, 3:09:28 PM - complete - 39.3 kB
Apr 16, 2025, 7:58:39 AM - complete - 78.1 kB
Apr 16, 2025, 7:58:38 AM - complete - 78.1 kB
Apr 8, 2025, 12:37:14 PM - complete - 76.9 kB
Starting URLs:
https://reactrouter.com/start/framework/installation
CSS Selector:
.markdown
Crawl Prefixes:
https://reactrouter.com/start/framework/
https://reactrouter.com/how-to/
https://reactrouter.com/explanation/
## Page: https://reactrouter.com/start/framework/installation # Installation * Framework * Data * Declarative ## Introduction Most projects start with a template. Let's use a basic template maintained by React Router: npx create-react-router@latest my-react-router-app Now change into the new directory and start the app 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 to see how to manually set up your project. We also have a number of ready to deploy templates available for you to get started with: npx create-react-router@latest --template remix-run/react-router-templates/<template-name> * * * Next: Routing --- ## Page: https://reactrouter.com/start/framework/routing # Routing * Framework * Data * Declarative ## 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. 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: 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. You can even combine different routing conventions if you like: 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: route("teams/:teamId", "./team.tsx"), // route module ^^^^^^^^ Here's a sample route module: // 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 ## Nested Routes Routes can be nested inside parent routes. 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. 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. 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 index(componentFile), Index routes render into their parent's Outlet at their parent's URL (like a default child route). 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 file. 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; ## 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. route("teams/:teamId", "./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: route("c/:categoryId/p/:productId", "./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. route(":lang?/categories", "./categories.tsx"), You can have optional static segments, too: 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. route("files/*", "./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`: const { "*": splat } = params; ## Component Routes You can also use components that match the URL to elements anywhere in the component tree: 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 --- ## Page: https://reactrouter.com/start/framework/route-module # Route Module * Framework * Data * Declarative ## Introduction The files referenced in `routes.ts` are called Route Modules. 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. 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 preferrable because they will be automatically typed correctly for the route. ### Using props 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> ); } ## `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. export async function loader() { return { message: "Hello, world!" }; } export default function MyRoute({ loaderData }) { return <h1>{loaderData.message}</h1>; } See also: * `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. 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: export async function clientLoader() { // ... } clientLoader.hydrate = true as const; 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`. See also: * `clientLoader` 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`. // 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 }; } ## `clientAction` Like route actions but only called in the browser. export async function clientAction({ serverAction }) { fakeInvalidateClientSideCache(); // can still call the server action if needed const data = await serverAction(); return data; } See also: * `clientAction` params ## `ErrorBoundary` When other route module APIs throw, the route module `ErrorBoundary` will render instead of the route component. 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>; } } ## `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. 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` Route headers define HTTP headers to be sent with the response when server rendering. export function headers() { return { "X-Stretchy-Pants": "its for fun", "Cache-Control": "max-age=300, s-maxage=3600", }; } ## `handle` Route handle allows apps to add anything to a route match in `useMatches` to create abstractions (like breadcrumbs, etc.). export const handle = { its: "all yours", }; ## `links` Route links define `<link>` elements to be rendered in the document `<head>`. 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: import { Links } from "react-router"; export default function Root() { return ( <html> <head> <Links /> </head> <body /> </html> ); } ## `meta` Route meta defines meta tags to be rendered in the `<head>` of the document. export function meta() { return [ { title: "Very cool app" }, { property: "og:title", content: "Very cool app", }, { name: "description", content: "This app is the best", }, ]; } All routes' meta will be aggregated and rendered through the `<Meta />` component, usually rendered in your app root: import { Meta } from "react-router"; export default function Root() { return ( <html> <head> <Meta /> </head> <body /> </html> ); } **See also** * `meta` params ## `shouldRevalidate` By default, all routes are revalidated after actions. This function allows a route to opt-out of revalidation for actions that don't affect its data. import type { ShouldRevalidateFunctionArgs } from "react-router"; export function shouldRevalidate( arg: ShouldRevalidateFunctionArgs ) { return true; } * * * Next: Rendering Strategies --- ## Page: https://reactrouter.com/start/framework/rendering # Rendering Strategies * Framework * Data * Declarative ## 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: import type { Config } from "@react-router/dev/config"; export default { ssr: false, } satisfies Config; ## Server Side Rendering 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 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 --- ## Page: https://reactrouter.com/start/framework/data-loading # Data Loading * Framework * Data * Declarative ## 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. ## 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. // 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. // 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. // 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: 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. // 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, ...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. 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 See also: * Streaming with Suspense * Client Data * Using Fetchers --- ## Page: https://reactrouter.com/start/framework/actions # Actions * Framework * Data * Declarative ## 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. // 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. // 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 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`. 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). 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. fetcher.submit( { title: "New Title" }, { action: "/update-task/123", method: "post" } ); See the Using Fetchers guide for more information. * * * Next: Navigating --- ## Page: https://reactrouter.com/start/framework/navigating # Navigating * Framework * Data * Declarative ## 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. 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: 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: // className <NavLink to="/messages" className={({ isActive, isPending, isTransitioning }) => [ isPending ? "pending" : "", isActive ? "active" : "", isTransitioning ? "transitioning" : "", ].join(" ") } > Messages </NavLink> // style <NavLink to="/messages" style={({ isActive, isPending, isTransitioning }) => { return { fontWeight: isActive ? "bold" : "", color: isPending ? "red" : "black", viewTransitionName: isTransitioning ? "slide" : "", }; }} > Messages </NavLink> // 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: 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. <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. ## redirect Inside of route loaders and actions, you can return a `redirect` to another URL. 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: 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. import { useNavigate } from "react-router"; export function useLogoutAfterInactivity() { let navigate = useNavigate(); useFakeInactivityHook(() => { navigate("/logout"); }); } * * * Next: Pending UI --- ## Page: https://reactrouter.com/start/framework/pending-ui # Pending UI * Framework * Data * Declarative ## 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`. 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. 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 form because it has it's own independent state (whereas normal forms cause a global navigation). 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`. 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. 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 --- ## Page: https://reactrouter.com/start/framework/testing # Testing * Framework * Data * Declarative ## 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` import { useActionData } from "react-router"; export function LoginForm() { const { errors } = useActionData(); 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. 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)); }); --- ## Page: https://reactrouter.com/how-to/client-data # Client Data You can fetch and mutate data directly in the browser using `clientLoader` and `clientAction` functions. These functions are the primary mechanism for data handling when using SPA mode. This guide demonstrates common use cases for leveraging client data in Server-Side Rendering (SSR). ## Skip the Server Hop When using React Router with a Backend-For-Frontend (BFF) architecture, you might want to bypass the React Router server and communicate directly with your backend API. This approach requires proper authentication handling and assumes no CORS restrictions. Here's how to implement this: 1. Load the data from server `loader` on the document load 2. Load the data from the `clientLoader` on all subsequent loads In this scenario, React Router will _not_ call the `clientLoader` on hydration - and will only call it on subsequent navigations. export async function loader({ request, }: Route.LoaderArgs) { const data = await fetchApiFromServer({ request }); // (1) return data; } export async function clientLoader({ request, }: Route.ClientLoaderArgs) { const data = await fetchApiFromClient({ request }); // (2) return data; } ## Fullstack State Sometimes you need to combine data from both the server and browser (like IndexedDB or browser SDKs) before rendering a component. Here's how to implement this pattern: 1. Load the partial data from server `loader` on the document load 2. Export a `HydrateFallback` component to render during SSR because we don't yet have a full set of data 3. Set `clientLoader.hydrate = true`, this instructs React Router to call the clientLoader as part of initial document hydration 4. Combine the server data with the client data in `clientLoader` export async function loader({ request, }: Route.LoaderArgs) { const partialData = await getPartialDataFromDb({ request, }); // (1) return partialData; } export async function clientLoader({ request, serverLoader, }: Route.ClientLoaderArgs) { const [serverData, clientData] = await Promise.all([ serverLoader(), getClientData(request), ]); return { ...serverData, // (4) ...clientData, // (4) }; } clientLoader.hydrate = true as const; // (3) export function HydrateFallback() { return <p>Skeleton rendered during SSR</p>; // (2) } export default function Component({ // This will always be the combined set of server + client data loaderData, }: Route.ComponentProps) { return <>...</>; } ## Choosing Server or Client Data Loading You can mix data loading strategies across your application, choosing between server-only or client-only data loading for each route. Here's how to implement both approaches: 1. Export a `loader` when you want to use server data 2. Export `clientLoader` and a `HydrateFallback` when you want to use client data A route that only depends on a server loader looks like this: export async function loader({ request, }: Route.LoaderArgs) { const data = await getServerData(request); return data; } export default function Component({ loaderData, // (1) - server data }: Route.ComponentProps) { return <>...</>; } A route that only depends on a client loader looks like this. export async function clientLoader({ request, }: Route.ClientLoaderArgs) { const clientData = await getClientData(request); return clientData; } // Note: you do not have to set this explicitly - it is implied if there is no `loader` clientLoader.hydrate = true; // (2) export function HydrateFallback() { return <p>Skeleton rendered during SSR</p>; } export default function Component({ loaderData, // (2) - client data }: Route.ComponentProps) { return <>...</>; } ## Client-Side Caching You can implement client-side caching (using memory, localStorage, etc.) to optimize server requests. Here's a pattern that demonstrates cache management: 1. Load the data from server `loader` on the document load 2. Set `clientLoader.hydrate = true` to prime the cache 3. Load subsequent navigations from the cache via `clientLoader` 4. Invalidate the cache in your `clientAction` Note that since we are not exporting a `HydrateFallback` component, we will SSR the route component and then run the `clientLoader` on hydration, so it's important that your `loader` and `clientLoader` return the same data on initial load to avoid hydration errors. export async function loader({ request, }: Route.LoaderArgs) { const data = await getDataFromDb({ request }); // (1) return data; } export async function action({ request, }: Route.ActionArgs) { await saveDataToDb({ request }); return { ok: true }; } let isInitialRequest = true; export async function clientLoader({ request, serverLoader, }: Route.ClientLoaderArgs) { const cacheKey = generateKey(request); if (isInitialRequest) { isInitialRequest = false; const serverData = await serverLoader(); cache.set(cacheKey, serverData); // (2) return serverData; } const cachedData = await cache.get(cacheKey); if (cachedData) { return cachedData; // (3) } const serverData = await serverLoader(); cache.set(cacheKey, serverData); return serverData; } clientLoader.hydrate = true; // (2) export async function clientAction({ request, serverAction, }: Route.ClientActionArgs) { const cacheKey = generateKey(request); cache.delete(cacheKey); // (4) const serverData = await serverAction(); return serverData; } --- ## Page: https://reactrouter.com/how-to/error-boundary # Error Boundaries To avoid rendering an empty page to users, route modules will automatically catch errors in your code and render the closest `ErrorBoundary`. Error boundaries are not intended for error reporting or rendering form validation errors. Please see Form Validation and Error Reporting instead. ## 1\. Add a root error boundary All applications should at a minimum export a root error boundary. This one handles the three main cases: * Thrown `data` with a status code and text * Instances of errors with a stack trace * Randomly thrown values import { Route } from "./+types/root"; export function ErrorBoundary({ error, }: Route.ErrorBoundaryProps) { if (isRouteErrorResponse(error)) { return ( <> <h1> {error.status} {error.statusText} </h1> <p>{error.data}</p> </> ); } 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>; } } ## 2\. Write a bug It's not recommended to intentionally throw errors to force the error boundary to render as a means of control flow. Error Boundaries are primarily for catching unintentional errors in your code. export async function loader() { return undefined(); } This will render the `instanceof Error` branch of the UI from step 1. This is not just for loaders, but for all route module APIs: loaders, actions, components, headers, links, and meta. ## 3\. Throw data in loaders/actions There are exceptions to the rule in #2, especially 404s. You can intentionally `throw data()` (with a proper status code) to the closest error boundary when your loader can't find what it needs to render the page. Throw a 404 and move on. import { data } from "react-router"; export async function loader({ params }) { let record = await fakeDb.getRecord(params.id); if (!record) { throw data("Record Not Found", { status: 404 }); } return record; } This will render the `isRouteErrorResponse` branch of the UI from step 1. ## 4\. Nested error boundaries When an error is thrown, the "closest error boundary" will be rendered. Consider these nested routes: // β has error boundary route("/app", "app.tsx", [ // β no error boundary route("invoices", "invoices.tsx", [ // β has error boundary route("invoices/:id", "invoice-page.tsx", [ // β no error boundary route("payments", "payments.tsx"), ]), ]), ]); The following table shows which error boundary will render given the origin of the error: | error origin | rendered boundary | | --- | --- | | app.tsx | app.tsx | | invoices.tsx | app.tsx | | invoice-page.tsx | invoice-page.tsx | | payments.tsx | invoice-page.tsx | ## Error Sanitization In production mode, any errors that happen on the server are automatically sanitized before being sent to the browser to prevent leaking any sensitive server information (like stack traces). This means that a thrown `Error` will have a generic message and no stack trace in production in the browser. The original error is untouched on the server. Also note that data sent with `throw data(yourData)` is not sanitized as the data there is intended to be rendered. --- ## Page: https://reactrouter.com/how-to/error-reporting # Error Reporting React Router catches errors in your route modules and sends them to error boundaries to prevent blank pages when errors occur. However, ErrorBoundary isn't sufficient for logging and reporting errors. To access these caught errors, use the handleError export of the server entry module. ## 1\. Reveal the server entry If you don't see `entry.server.tsx` in your app directory, you're using a default entry. Reveal it with this cli command: react-router reveal ## 2\. Export your error handler This function is called whenever React Router catches an error in your application on the server. import { type HandleErrorFunction } from "react-router"; export const handleError: HandleErrorFunction = ( error, { request } ) => { // React Router may abort some interrupted requests, don't log those if (!request.signal.aborted) { myReportError(error); // make sure to still log the error so you can see it console.error(error); } }; --- ## Page: https://reactrouter.com/how-to/fetchers # Using Fetchers Fetchers are useful for creating complex, dynamic user interfaces that require multiple, concurrent data interactions without causing a navigation. Fetchers track their own, independent state and can be used to load data, mutate data, submit forms, and generally interact with loaders and actions. ## Calling Actions The most common case for a fetcher is to submit data to an action, triggering a revalidation of route data. Consider the following route module: import { useLoaderData } from "react-router"; export async function clientLoader({ request }) { let title = localStorage.getItem("title") || "No Title"; return { title }; } export default function Component() { let data = useLoaderData(); return ( <div> <h1>{data.title}</h1> </div> ); } ### 1\. Add an action First we'll add an action to the route for the fetcher to call: import { useLoaderData } from "react-router"; export async function clientLoader({ request }) { // ... } export async function clientAction({ request }) { await new Promise((res) => setTimeout(res, 1000)); let data = await request.formData(); localStorage.setItem("title", data.get("title")); return { ok: true }; } export default function Component() { let data = useLoaderData(); // ... } ### 2\. Create a fetcher Next create a fetcher and render a form with it: import { useLoaderData, useFetcher } from "react-router"; // ... export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); return ( <div> <h1>{data.title}</h1> <fetcher.Form method="post"> <input type="text" name="title" /> </fetcher.Form> </div> ); } ### 3\. Submit the form If you submit the form now, the fetcher will call the action and revalidate the route data automatically. ### 4\. Render pending state Fetchers make their state available during the async work so you can render pending UI the moment the user interacts: export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); return ( <div> <h1>{data.title}</h1> <fetcher.Form method="post"> <input type="text" name="title" /> {fetcher.state !== "idle" && <p>Saving...</p>} </fetcher.Form> </div> ); } ### 5\. Optimistic UI Sometimes there's enough information in the form to render the next state immediately. You can access the form data with `fetcher.formData`: export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); let title = fetcher.formData?.get("title") || data.title; return ( <div> <h1>{title}</h1> <fetcher.Form method="post"> <input type="text" name="title" /> {fetcher.state !== "idle" && <p>Saving...</p>} </fetcher.Form> </div> ); } ### 6\. Fetcher Data and Validation Data returned from an action is available in the fetcher's `data` property. This is primarily useful for returning error messages to the user for a failed mutation: // ... export async function clientAction({ request }) { await new Promise((res) => setTimeout(res, 1000)); let data = await request.formData(); let title = data.get("title") as string; if (title.trim() === "") { return { ok: false, error: "Title cannot be empty" }; } localStorage.setItem("title", title); return { ok: true, error: null }; } export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); let title = fetcher.formData?.get("title") || data.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> )} </fetcher.Form> </div> ); } ## Loading Data Another common use case for fetchers is to load data from a route for something like a combobox. ### 1\. Create a search route Consider the following route with a very basic search: // { path: '/search-users', filename: './search-users.tsx' } const users = [ { id: 1, name: "Ryan" }, { id: 2, name: "Michael" }, // ... ]; export async function loader({ request }) { await new Promise((res) => setTimeout(res, 300)); let url = new URL(request.url); let query = url.searchParams.get("q"); return users.filter((user) => user.name.toLowerCase().includes(query.toLowerCase()) ); } ### 2\. Render a fetcher in a combobox component import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher(); return ( <div> <fetcher.Form method="get" action="/search-users"> <input type="text" name="q" /> </fetcher.Form> </div> ); } * The action points to the route we created above: "/search-users" * The name of the input is "q" to match the query parameter ### 3\. Add type inference import { useFetcher } from "react-router"; import type { Search } from "./search-users"; export function UserSearchCombobox() { let fetcher = useFetcher<typeof Search.action>(); // ... } Ensure you use `import type` so you only import the types. ### 4\. Render the data import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher<typeof Search.action>(); return ( <div> <fetcher.Form method="get" action="/search-users"> <input type="text" name="q" /> </fetcher.Form> {fetcher.data && ( <ul> {fetcher.data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> )} </div> ); } Note you will need to hit "enter" to submit the form and see the results. ### 5\. Render a pending state import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher<typeof Search.action>(); return ( <div> <fetcher.Form method="get" action="/search-users"> <input type="text" name="q" /> </fetcher.Form> {fetcher.data && ( <ul style={{ opacity: fetcher.state === "idle" ? 1 : 0.25, }} > {fetcher.data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> )} </div> ); } ### 6\. Search on user input Fetchers can be submitted programmatically with `fetcher.submit`: <fetcher.Form method="get" action="/search-users"> <input type="text" name="q" onChange={(event) => { fetcher.submit(event.currentTarget.form); }} /> </fetcher.Form> Note the input event's form is passed as the first argument to `fetcher.submit`. The fetcher will use that form to submit the request, reading its attributes and serializing the data from its elements. --- ## Page: https://reactrouter.com/how-to/file-route-conventions # File Route Conventions The `@react-router/fs-routes` package enables file-convention based route config. ## Setting up First install the `@react-router/fs-routes` package: npm i @react-router/fs-routes Then use it to provide route config in your `app/routes.ts` file: import { type RouteConfig } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default flatRoutes() satisfies RouteConfig; Any modules in the `app/routes` directory will become routes in your application by default. The `ignoredRouteFiles` option allows you to specify files that should not be included as routes: import { type RouteConfig } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default flatRoutes({ ignoredRouteFiles: ["home.tsx"], }) satisfies RouteConfig; This will look for routes in the `app/routes` directory by default, but this can be configured via the `rootDirectory` option which is relative to your app directory: import { type RouteConfig } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default flatRoutes({ rootDirectory: "file-routes", }) satisfies RouteConfig; The rest of this guide will assume you're using the default `app/routes` directory. ## Basic Routes The filename maps to the route's URL pathname, except for `_index.tsx` which is the index route for the root route. You can use `.js`, `.jsx`, `.ts` or `.tsx` file extensions. app/ βββ routes/ β βββ _index.tsx β βββ about.tsx βββ root.tsx | URL | Matched Routes | | --- | --- | | `/` | `app/routes/_index.tsx` | | `/about` | `app/routes/about.tsx` | Note that these routes will be rendered in the outlet of `app/root.tsx` because of nested routing. ## Dot Delimiters Adding a `.` to a route filename will create a `/` in the URL. app/ βββ routes/ β βββ _index.tsx β βββ about.tsx β βββ concerts.trending.tsx β βββ concerts.salt-lake-city.tsx β βββ concerts.san-diego.tsx βββ root.tsx | URL | Matched Route | | --- | --- | | `/` | `app/routes/_index.tsx` | | `/about` | `app/routes/about.tsx` | | `/concerts/trending` | `app/routes/concerts.trending.tsx` | | `/concerts/salt-lake-city` | `app/routes/concerts.salt-lake-city.tsx` | | `/concerts/san-diego` | `app/routes/concerts.san-diego.tsx` | The dot delimiter also creates nesting, see the nesting section for more information. ## Dynamic Segments Usually your URLs aren't static but data-driven. Dynamic segments allow you to match segments of the URL and use that value in your code. You create them with the `$` prefix. app/ βββ routes/ β βββ _index.tsx β βββ about.tsx β βββ concerts.$city.tsx β βββ concerts.trending.tsx βββ root.tsx | URL | Matched Route | | --- | --- | | `/` | `app/routes/_index.tsx` | | `/about` | `app/routes/about.tsx` | | `/concerts/trending` | `app/routes/concerts.trending.tsx` | | `/concerts/salt-lake-city` | `app/routes/concerts.$city.tsx` | | `/concerts/san-diego` | `app/routes/concerts.$city.tsx` | The value will be parsed from the URL and passed to various APIs. We call these values "URL Parameters". The most useful places to access the URL params are in loaders and actions. export async function serverLoader({ params }) { return fakeDb.getAllConcertsForCity(params.city); } You'll note the property name on the `params` object maps directly to the name of your file: `$city.tsx` becomes `params.city`. Routes can have multiple dynamic segments, like `concerts.$city.$date`, both are accessed on the params object by name: export async function serverLoader({ params }) { return fake.db.getConcerts({ date: params.date, city: params.city, }); } See the routing guide for more information. ## Nested Routes Nested Routing is the general idea of coupling segments of the URL to component hierarchy and data. You can read more about it in the Routing Guide. You create nested routes with dot delimiters. If the filename before the `.` matches another route filename, it automatically becomes a child route to the matching parent. Consider these routes: app/ βββ routes/ β βββ _index.tsx β βββ about.tsx β βββ concerts._index.tsx β βββ concerts.$city.tsx β βββ concerts.trending.tsx β βββ concerts.tsx βββ root.tsx All the routes that start with `app/routes/concerts.` will be child routes of `app/routes/concerts.tsx` and render inside the parent route's outlet. | URL | Matched Route | Layout | | --- | --- | --- | | `/` | `app/routes/_index.tsx` | `app/root.tsx` | | `/about` | `app/routes/about.tsx` | `app/root.tsx` | | `/concerts` | `app/routes/concerts._index.tsx` | `app/routes/concerts.tsx` | | `/concerts/trending` | `app/routes/concerts.trending.tsx` | `app/routes/concerts.tsx` | | `/concerts/salt-lake-city` | `app/routes/concerts.$city.tsx` | `app/routes/concerts.tsx` | Note you typically want to add an index route when you add nested routes so that something renders inside the parent's outlet when users visit the parent URL directly. For example, if the URL is `/concerts/salt-lake-city` then the UI hierarchy will look like this: <Root> <Concerts> <City /> </Concerts> </Root> ## Nested URLs without Layout Nesting Sometimes you want the URL to be nested, but you don't want the automatic layout nesting. You can opt out of nesting with a trailing underscore on the parent segment: app/ βββ routes/ β βββ _index.tsx β βββ about.tsx β βββ concerts.$city.tsx β βββ concerts.trending.tsx β βββ concerts.tsx β βββ concerts_.mine.tsx βββ root.tsx | URL | Matched Route | Layout | | --- | --- | --- | | `/` | `app/routes/_index.tsx` | `app/root.tsx` | | `/about` | `app/routes/about.tsx` | `app/root.tsx` | | `/concerts/mine` | `app/routes/concerts_.mine.tsx` | `app/root.tsx` | | `/concerts/trending` | `app/routes/concerts.trending.tsx` | `app/routes/concerts.tsx` | | `/concerts/salt-lake-city` | `app/routes/concerts.$city.tsx` | `app/routes/concerts.tsx` | Note that `/concerts/mine` does not nest with `app/routes/concerts.tsx` anymore, but `app/root.tsx`. The `trailing_` underscore creates a path segment, but it does not create layout nesting. Think of the `trailing_` underscore as the long bit at the end of your parent's signature, writing you out of the will, removing the segment that follows from the layout nesting. ## Nested Layouts without Nested URLs We call these **Pathless Routes** Sometimes you want to share a layout with a group of routes without adding any path segments to the URL. A common example is a set of authentication routes that have a different header/footer than the public pages or the logged in app experience. You can do this with a `_leading` underscore. app/ βββ routes/ β βββ _auth.login.tsx β βββ _auth.register.tsx β βββ _auth.tsx β βββ _index.tsx β βββ concerts.$city.tsx β βββ concerts.tsx βββ root.tsx | URL | Matched Route | Layout | | --- | --- | --- | | `/` | `app/routes/_index.tsx` | `app/root.tsx` | | `/login` | `app/routes/_auth.login.tsx` | `app/routes/_auth.tsx` | | `/register` | `app/routes/_auth.register.tsx` | `app/routes/_auth.tsx` | | `/concerts` | `app/routes/concerts.tsx` | `app/routes/concerts.tsx` | | `/concerts/salt-lake-city` | `app/routes/concerts.$city.tsx` | `app/routes/concerts.tsx` | Think of the `_leading` underscore as a blanket you're pulling over the filename, hiding the filename from the URL. ## Optional Segments Wrapping a route segment in parentheses will make the segment optional. app/ βββ routes/ β βββ ($lang)._index.tsx β βββ ($lang).$productId.tsx β βββ ($lang).categories.tsx βββ root.tsx | URL | Matched Route | | --- | --- | | `/` | `app/routes/($lang)._index.tsx` | | `/categories` | `app/routes/($lang).categories.tsx` | | `/en/categories` | `app/routes/($lang).categories.tsx` | | `/fr/categories` | `app/routes/($lang).categories.tsx` | | `/american-flag-speedo` | `app/routes/($lang)._index.tsx` | | `/en/american-flag-speedo` | `app/routes/($lang).$productId.tsx` | | `/fr/american-flag-speedo` | `app/routes/($lang).$productId.tsx` | You may wonder why `/american-flag-speedo` is matching the `($lang)._index.tsx` route instead of `($lang).$productId.tsx`. This is because when you have an optional dynamic param segment followed by another dynamic param, it cannot reliably be determined if a single-segment URL such as `/american-flag-speedo` should match `/:lang` `/:productId`. Optional segments match eagerly and thus it will match `/:lang`. If you have this type of setup it's recommended to look at `params.lang` in the `($lang)._index.tsx` loader and redirect to `/:lang/american-flag-speedo` for the current/default language if `params.lang` is not a valid language code. ## Splat Routes While dynamic segments match a single path segment (the stuff between two `/` in a URL), a splat route will match the rest of a URL, including the slashes. app/ βββ routes/ β βββ _index.tsx β βββ $.tsx β βββ about.tsx β βββ files.$.tsx βββ root.tsx | URL | Matched Route | | --- | --- | | `/` | `app/routes/_index.tsx` | | `/about` | `app/routes/about.tsx` | | `/beef/and/cheese` | `app/routes/$.tsx` | | `/files` | `app/routes/files.$.tsx` | | `/files/talks/react-conf_old.pdf` | `app/routes/files.$.tsx` | | `/files/talks/react-conf_final.pdf` | `app/routes/files.$.tsx` | | `/files/talks/react-conf-FINAL-MAY_2024.pdf` | `app/routes/files.$.tsx` | Similar to dynamic route parameters, you can access the value of the matched path on the splat route's `params` with the `"*"` key. export async function serverLoader({ params }) { const filePath = params["*"]; return fake.getFileInfo(filePath); } ## Escaping Special Characters If you want one of the special characters used for these route conventions to actually be a part of the URL, you can escape the conventions with `[]` characters. This can be especially helpful for resource routes that include an extension in the URL. | Filename | URL | | --- | --- | | `app/routes/sitemap[.]xml.tsx` | `/sitemap.xml` | | `app/routes/[sitemap.xml].tsx` | `/sitemap.xml` | | `app/routes/weird-url.[_index].tsx` | `/weird-url/_index` | | `app/routes/dolla-bills-[$].tsx` | `/dolla-bills-$` | | `app/routes/[[so-weird]].tsx` | `/[so-weird]` | | `app/routes/reports.$id[.pdf].ts` | `/reports/123.pdf` | ## Folders for Organization Routes can also be folders with a `route.tsx` file inside defining the route module. The rest of the files in the folder will not become routes. This allows you to organize your code closer to the routes that use them instead of repeating the feature names across other folders. The files inside a folder have no meaning for the route paths, the route path is completely defined by the folder name. Consider these routes: app/ βββ routes/ β βββ _landing._index.tsx β βββ _landing.about.tsx β βββ _landing.tsx β βββ app._index.tsx β βββ app.projects.tsx β βββ app.tsx β βββ app_.projects.$id.roadmap.tsx βββ root.tsx Some, or all of them can be folders holding their own `route` module inside. app/ βββ routes/ β βββ _landing._index/ β β βββ route.tsx β β βββ scroll-experience.tsx β βββ _landing.about/ β β βββ employee-profile-card.tsx β β βββ get-employee-data.server.ts β β βββ route.tsx β β βββ team-photo.jpg β βββ _landing/ β β βββ footer.tsx β β βββ header.tsx β β βββ route.tsx β βββ app._index/ β β βββ route.tsx β β βββ stats.tsx β βββ app.projects/ β β βββ get-projects.server.ts β β βββ project-buttons.tsx β β βββ project-card.tsx β β βββ route.tsx β βββ app/ β β βββ footer.tsx β β βββ primary-nav.tsx β β βββ route.tsx β βββ app_.projects.$id.roadmap/ β β βββ chart.tsx β β βββ route.tsx β β βββ update-timeline.server.ts β βββ contact-us.tsx βββ root.tsx Note that when you turn a route module into a folder, the route module becomes `folder/route.tsx`, all other modules in the folder will not become routes. For example: # these are the same route: app/routes/app.tsx app/routes/app/route.tsx # as are these app/routes/app._index.tsx app/routes/app._index/route.tsx --- ## Page: https://reactrouter.com/how-to/file-uploads # File Uploads Handle file uploads in your React Router applications. This guide uses some packages from the Remix The Web project to make file uploads easier. _Thank you to David Adams for writing an original guide on which this doc is based. You can refer to it for even more examples._ ## Basic File Upload ### 1\. Setup some routes You can setup your routes however you like. This example uses the following structure: import { type RouteConfig, route, } from "@react-router/dev/routes"; export default [ // ... other routes route("user/:id", "pages/user-profile.tsx", [ route("avatar", "api/avatar.tsx"), ]), ] satisfies RouteConfig; ### 2\. Add the form data parser `form-data-parser` is a wrapper around `request.formData()` that provides streaming support for handling file uploads. npm i @mjackson/form-data-parser See the `form-data-parser` docs for more information ### 3\. Create a route with an upload action The `parseFormData` function takes an `uploadHandler` function as an argument. This function will be called for each file upload in the form. You must set the form's `enctype` to `multipart/form-data` for file uploads to work. import { type FileUpload, parseFormData, } from "@mjackson/form-data-parser"; export async function action({ request, }: ActionFunctionArgs) { const uploadHandler = async (fileUpload: FileUpload) => { if (fileUpload.fieldName === "avatar") { // process the upload and return a File } }; const formData = await parseFormData( request, uploadHandler ); // 'avatar' has already been processed at this point const file = formData.get("avatar"); } export default function Component() { return ( <form method="post" encType="multipart/form-data"> <input type="file" name="avatar" /> <button>Submit</button> </form> ); } ## Local Storage Implementation ### 1\. Add the storage package `file-storage` is a key/value interface for storing File objects in JavaScript. Similar to how `localStorage` allows you to store key/value pairs of strings in the browser, file-storage allows you to store key/value pairs of files on the server. npm i @mjackson/file-storage See the `file-storage` docs for more information ### 2\. Create a storage configuration Create a file that exports a `LocalFileStorage` instance to be used by different routes. import { LocalFileStorage } from "@mjackson/file-storage/local"; export const fileStorage = new LocalFileStorage( "./uploads/avatars" ); export function getStorageKey(userId: string) { return `user-${userId}-avatar`; } ### 3\. Implement the upload handler Update the form's `action` to store files in the `fileStorage` instance. import { type FileUpload, parseFormData, } from "@mjackson/form-data-parser"; import { fileStorage, getStorageKey, } from "~/avatar-storage.server"; import type { Route } from "./+types/user-profile"; export async function action({ request, params, }: Route.ActionArgs) { async function uploadHandler(fileUpload: FileUpload) { if ( fileUpload.fieldName === "avatar" && fileUpload.type.startsWith("image/") ) { let storageKey = getStorageKey(params.id); // FileUpload objects are not meant to stick around for very long (they are // streaming data from the request.body); store them as soon as possible. await fileStorage.set(storageKey, fileUpload); // Return a File for the FormData object. This is a LazyFile that knows how // to access the file's content if needed (using e.g. file.stream()) but // waits until it is requested to actually read anything. return fileStorage.get(storageKey); } } const formData = await parseFormData( request, uploadHandler ); } export default function UserPage({ actionData, params, }: Route.ComponentProps) { return ( <div> <h1>User {params.id}</h1> <form method="post" // The form's enctype must be set to "multipart/form-data" for file uploads encType="multipart/form-data" > <input type="file" name="avatar" accept="image/*" /> <button>Submit</button> </form> <img src={`/user/${params.id}/avatar`} alt="user avatar" /> </div> ); } ### 4\. Add a route to serve the uploaded file Create a resource route that streams the file as a response. import { fileStorage, getStorageKey, } from "~/avatar-storage.server"; import type { Route } from "./+types/avatar"; export async function loader({ params }: Route.LoaderArgs) { const storageKey = getStorageKey(params.id); const file = await fileStorage.get(storageKey); if (!file) { throw new Response("User avatar not found", { status: 404, }); } return new Response(file.stream(), { headers: { "Content-Type": file.type, "Content-Disposition": `attachment; filename=${file.name}`, }, }); } --- ## Page: https://reactrouter.com/how-to/form-validation # Form Validation This guide walks through a simple signup form implementation. You will likely want to pair these concepts with third-party validation libraries and error components, but this guide only focuses on the moving pieces for React Router. ## 1\. Setting Up We'll start by creating a basic signup route with form. import { type RouteConfig, route, } from "@react-router/dev/routes"; export default [ route("signup", "signup.tsx"), ] satisfies RouteConfig; import type { Route } from "./+types/signup"; import { useFetcher } from "react-router"; export default function Signup(_: Route.ComponentProps) { let fetcher = useFetcher(); return ( <fetcher.Form method="post"> <p> <input type="email" name="email" /> </p> <p> <input type="password" name="password" /> </p> <button type="submit">Sign Up</button> </fetcher.Form> ); } ## 2\. Defining the Action In this step, we'll define a server `action` in the same file as our `Signup` component. Note that the aim here is to provide a broad overview of the mechanics involved rather than digging deep into form validation rules or error object structures. We'll use rudimentary checks for the email and password to demonstrate the core concepts. import type { Route } from "./+types/signup"; import { redirect, useFetcher, data } from "react-router"; export default function Signup(_: Route.ComponentProps) { // omitted for brevity } 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 = {}; if (!email.includes("@")) { errors.email = "Invalid email address"; } if (password.length < 12) { errors.password = "Password should be at least 12 characters"; } if (Object.keys(errors).length > 0) { return data({ errors }, { status: 400 }); } // Redirect to dashboard if validation is successful return redirect("/dashboard"); } If any validation errors are found, they are returned from the `action` to the fetcher. This is our way of signaling to the UI that something needs to be corrected, otherwise the user will be redirected to the dashboard. Note the `data({ errors }, { status: 400 })` call. Setting a 400 status is the web standard way to signal to the client that there was a validation error (Bad Request). In React Router, only 200 status codes trigger page data revalidation so a 400 prevent that. ## 3\. Displaying Validation Errors Finally, we'll modify the `Signup` component to display validation errors, if any, from `fetcher.data`. export default function Signup(_: Route.ComponentProps) { let fetcher = useFetcher(); let errors = fetcher.data?.errors; return ( <fetcher.Form method="post"> <p> <input type="email" name="email" /> {errors?.email ? <em>{errors.email}</em> : null} </p> <p> <input type="password" name="password" /> {errors?.password ? ( <em>{errors.password}</em> ) : null} </p> <button type="submit">Sign Up</button> </fetcher.Form> ); } --- ## Page: https://reactrouter.com/how-to/headers # HTTP Headers Headers are primarily defined with the route module `headers` export. You can also set headers in `entry.server.tsx`. ## From Route Modules import { Route } from "./+types/some-route"; export function headers(_: Route.HeadersArgs) { return { "Content-Security-Policy": "default-src 'self'", "X-Frame-Options": "DENY", "X-Content-Type-Options": "nosniff", "Cache-Control": "max-age=3600, s-maxage=86400", }; } You can return either a `Headers` instance or `HeadersInit`. ## From loaders and actions When the header is dependent on loader data, loaders and actions can also set headers. ### 1\. Wrap your return value in `data` import { data } from "react-router"; export async function loader({ params }: LoaderArgs) { let [page, ms] = await fakeTimeCall( await getPage(params.id) ); return data(page, { headers: { "Server-Timing": `page;dur=${ms};desc="Page query"`, }, }); } ### 2\. Return from `headers` export Headers from loaders and actions are not sent automatically. You must explicitly return them from the `headers` export. export function headers({ actionHeaders, loaderHeaders, }: HeadersArgs) { return actionHeaders ? actionHeaders : loaderHeaders; } One notable exception is `Set-Cookie` headers, which are automatically preserved from `headers`, `loader`, and `action` in parent routes, even without exporting `headers` from the child route. ## Merging with parent headers Consider these nested routes route("pages", "pages-layout-with-nav.tsx", [ route(":slug", "page.tsx"), ]); If both route modules want to set headers, the headers from the deepest matching route will be sent. When you need to keep both the parent and the child headers, you need to merge them in the child route. ### Appending The easiest way is to simply append to the parent headers. This avoids overwriting a header the parent may have set and both are important. export function headers({ parentHeaders }: HeadersArgs) { parentHeaders.append( "Permissions-Policy: geolocation=()" ); return parentHeaders; } ### Setting Sometimes it's important to overwrite the parent header. Do this with `set` instead of `append`: export function headers({ parentHeaders }: HeadersArgs) { parentHeaders.set( "Cache-Control", "max-age=3600, s-maxage=86400" ); return parentHeaders; } You can avoid the need to merge headers by only defining headers in "leaf routes" (index routes and child routes without children) and not in parent routes. ## From `entry.server.tsx` The `handleRequest` export receives the headers from the route module as an argument. You can append global headers here. export default function handleRequest( request, responseStatusCode, responseHeaders, routerContext, loadContext ) { // set, append global headers responseHeaders.set( "X-App-Version", routerContext.manifest.version ); return new Response(await getStream(), { headers: responseHeaders, status: responseStatusCode, }); } If you don't have an `entry.server.tsx` run the `reveal` command: react-router reveal --- ## Page: https://reactrouter.com/how-to/pre-rendering # Pre-Rendering Pre-Rendering allows you to speed up page loads for static content by rendering pages at build time instead of at runtime. Pre-rendering is enabled via the `prerender` config in `react-router.config.ts` and can be used in two ways based on the `ssr` config value: * Alongside a runtime SSR server with `ssr:true` (the default value) * Deployed to a static file server with `ssr:false` ## Pre-rendering with `ssr:true` ### Configuration Add the `prerender` option to your config, there are three signatures: import type { Config } from "@react-router/dev/config"; export default { // Can be omitted - defaults to true ssr: true, // all static paths (no dynamic segments like "/post/:slug") prerender: true, // specific paths prerender: ["/", "/blog", "/blog/popular-post"], // async function for dependencies like a CMS async prerender({ getStaticPaths }) { let posts = await fakeGetPostsFromCMS(); return [ "/", "/blog", ...posts.map((post) => post.href), ]; }, } satisfies Config; ### Data Loading and Pre-rendering There is no extra application API for pre-rendering. Routes being pre-rendered use the same route `loader` functions as server rendering: export async function loader({ request, params }) { let post = await getPost(params.slug); return post; } export function Post({ loaderData }) { return <div>{loaderData.title}</div>; } Instead of a request coming to your route on a deployed server, the build creates a `new Request()` and runs it through your app just like a server would. When server rendering, requests to paths that have not been pre-rendered will be server rendered as usual. ### Static File Output The rendered result will be written out to your `build/client` directory. You'll notice two files for each path: * `[url].html` HTML file for initial document requests * `[url].data` file for client side navigation browser requests The output of your build will indicate what files were pre-rendered: > react-router build vite v5.2.11 building for production... ... vite v5.2.11 building SSR bundle for production... ... Prerender: Generated build/client/index.html Prerender: Generated build/client/blog.data Prerender: Generated build/client/blog/index.html Prerender: Generated build/client/blog/my-first-post.data Prerender: Generated build/client/blog/my-first-post/index.html ... During development, pre-rendering doesn't save the rendered results to the public directory, this only happens for `react-router build`. ## Pre-rendering with `ssr:false` The above examples assume you are deploying a runtime server but are pre-rendering some static pages to avoid hitting the server, resulting in faster loads. To disable runtime SSR and configure pre-rendering to be served from a static file server, you can set the `ssr:false` config flag: import type { Config } from "@react-router/dev/config"; export default { ssr: false, // disable runtime server rendering prerender: true, // pre-render all static routes } satisfies Config; If you specify `ssr:false` without a `prerender` config, React Router refers to that as SPA Mode. In SPA Mode, we render a single HTML file that is capable of hydrating for _any_ of your application paths. It can do this because it only renders the `root` route into the HTML file and then determines which child routes to load based on the browser URL during hydration. This means you can use a `loader` on the root route, but not on any other routes because we don't know which routes to load until hydration in the browser. If you want to pre-render paths with `ssr:false`, those matched routes _can_ have loaders because we'll pre-render all of the matched routes for those paths, not just the root. You cannot include `actions` or `headers` functions in any routes when `ssr:false` is set because there will be no runtime server to run them on. ### Pre-rendering with a SPA Fallback If you want `ssr:false` but don't want to pre-render _all_ of your routes - that's fine too! You may have some paths where you need the performance/SEO benefits of pre-rendering, but other pages where a SPA would be fine. You can do this using the combination of config options as well - just limit your `prerender` config to the paths that you want to pre-render and React Router will also output a "SPA Fallback" HTML file that can be served to hydrate any other paths (using the same approach as SPA Mode). This will be written to one of the following paths: * `build/client/index.html` - If the `/` path is not pre-rendered * `build/client/__spa-fallback.html` - If the `/` path is pre-rendered import type { Config } from "@react-router/dev/config"; export default { ssr: false, // SPA fallback will be written to build/client/index.html prerender: ["/about-us"], // SPA fallback will be written to build/client/__spa-fallback.html prerender: ["/", "/about-us"], } satisfies Config; You can configure your deployment server to serve this file for any path that otherwise would 404. Some hosts do this by default, but others don't. As an example, a host may support a `_redirects` file to do this: # If you did not pre-render the `/` route /* /index.html 200 # If you pre-rendered the `/` route /* /__spa-fallback.html 200 If you're getting 404s at valid routes for your app, it's likely you need to configure your host. Here's another example of how you can do this with the `sirv-cli` tool: # If you did not pre-render the `/` route sirv-cli build/client --single index.html # If you pre-rendered the `/` route sirv-cli build/client --single __spa-fallback.html ### Invalid Exports When pre-rendering with `ssr:false`, React Router will error at build time if you have invalid exports to help prevent some mistakes that can be easily overlooked. * `headers`/`action` functions are prohibited in all routes because there will be no runtime server on which to run them * When using `ssr:false` without a `prerender` config (SPA Mode), a `loader` is permitted on the root route only * When using `ssr:false` with a `prerender` config, a `loader` is permitted on any route matched by a `prerender` path * If you are using a `loader` on a pre-rendered route that has child routes, you will need to make sure the parent `loaderData` can be determined at run-time properly by either: * Pre-rendering all child routes so that the parent `loader` can be called at build-time for each child route path and rendered into a `.data` file, or * Use a `clientLoader` on the parent that can be called at run-time for non-pre-rendered child paths --- ## Page: https://reactrouter.com/how-to/resource-routes # Resource Routes When server rendering, routes can serve "resources" instead of rendering components, like images, PDFs, JSON payloads, webhooks, etc. ## Defining a Resource Route A route becomes a resource route by convention when its module exports a loader or action but does not export a default component. Consider a route that serves a PDF instead of UI: route("/reports/pdf/:id", "pdf-report.ts"); import type { Route } from "./+types/pdf-report"; export async function loader({ params }: Route.LoaderArgs) { const report = await getReport(params.id); const pdf = await generateReportPDF(report); return new Response(pdf, { status: 200, headers: { "Content-Type": "application/pdf", }, }); } Note there is no default export. That makes this route a resource route. ## Linking to Resource Routes When linking to resource routes, use `<a>` or `<Link reloadDocument>`, otherwise React Router will attempt to use client side routing and fetching the payload (you'll get a helpful error message if you make this mistake). <Link reloadDocument to="/reports/pdf/123"> View as PDF </Link> ## Handling different request methods GET requests are handled by the `loader`, while POST, PUT, PATCH, and DELETE are handled by the `action`: import type { Route } from "./+types/resource"; export function loader(_: Route.LoaderArgs) { return Response.json({ message: "I handle GET" }); } export function action(_: Route.ActionArgs) { return Response.json({ message: "I handle everything else", }); } --- ## Page: https://reactrouter.com/how-to/route-module-type-safety # Route Module Type Safety React Router generates route-specific types to power type inference for URL params, loader data, and more. This guide will help you set it up if you didn't start with a template. To learn more about how type safety works in React Router, check out Type Safety Explanation. ## 1\. Add `.react-router/` to `.gitignore` React Router generates types into a `.react-router/` directory at the root of your app. This directory is fully managed by React Router and should be gitignore'd. .react-router/ ## 2\. Include the generated types in tsconfig Edit your tsconfig to get TypeScript to use the generated types. Additionally, `rootDirs` needs to be configured so the types can be imported as relative siblings to route modules. { "include": [".react-router/types/**/*"], "compilerOptions": { "rootDirs": [".", "./.react-router/types"] } } If you are using multiple `tsconfig` files for your app, you'll need to make these changes in whichever one `include`s your app directory. For example, the `node-custom-server` template contains `tsconfig.json`, `tsconfig.node.json`, and `tsconfig.vite.json`. Since `tsconfig.vite.json` is the one that includes the app directory, that's the one that sets up `.react-router/types` for route module type safety. ## 3\. Generate types before type checking If you want to run type checking as its own command β for example, as part of your Continuous Integration pipeline β you'll need to make sure to generate types _before_ running typechecking: { "scripts": { "typecheck": "react-router typegen && tsc" } } ## 4\. Typing `AppLoadContext` ## Extending app `Context` types To define your app's `context` type, add the following in a `.ts` or `.d.ts` file within your project: import "react-router"; declare module "react-router" { interface AppLoadContext { // add context properties here } } ## 5\. Type-only auto-imports (optional) When auto-importing the `Route` type helper, TypeScript will generate: import { Route } from "./+types/my-route"; But if you enable verbatimModuleSyntax: { "compilerOptions": { "verbatimModuleSyntax": true } } Then, you will get the `type` modifier for the import automatically as well: import type { Route } from "./+types/my-route"; // ^^^^ This helps tools like bundlers to detect type-only module that can be safely excluded from the bundle. ## Conclusion React Router's Vite plugin should be automatically generating types into `.react-router/types/` anytime you edit your route config (`routes.ts`). That means all you need to do is run `react-router dev` (or your custom dev server) to get to up-to-date types in your routes. Check out our Type Safety Explanation for an example of how to pull in those types into your routes. --- ## Page: https://reactrouter.com/how-to/security # Security This is by no means a comprehensive guide, but React Router provides features to help address a few aspects under the _very large_ umbrella that is _Security_. ## `Content-Security-Policy` If you are implementing a Content-Security-Policy (CSP) in your application, specifically one using the `unsafe-inline` directive, you will need to specify a `nonce` attribute on the inline `<script>` elements rendered in your HTML. This must be specified on any API that generates inline scripts, including: * `<Scripts nonce>` (`root.tsx`) * `<ScrollRestoration nonce>` (`root.tsx`) * `<ServerRouter nonce>` (`entry.server.tsx`) * `renderToPipeableStream(..., { nonce })` (`entry.server.tsx`) * `renderToReadableStream(..., { nonce })` (`entry.server.tsx`) --- ## Page: https://reactrouter.com/how-to/spa # Single Page App (SPA) There are two ways to ship a single page app with React Router * **as a library** - Instead of using React Router's framework features, you can use it as a library in your own SPA architecture. Refer to React Router as a Library guides. * **as a framework** - This guide will focus here ## Overview When using React Router as a framework, you can enable "SPA Mode" by setting `ssr:false` in your `react-router.config.ts` file. This will disable runtime server rendering and generate an `index.html` at build time that you can serve and hydrate as a SPA. Typical Single Page apps send a mostly blank `index.html` template with little more than an empty `<div id="root"></div>`. In contrast, `react-router build` (in SPA Mode) pre-renders your root route at build time into an `index.html` file. This means you can: * Send more than an empty `<div>` * Use a root `loader` to load data for your application shell * Use React components to generate the initial page users see (root `HydrateFallback`) * Re-enable server rendering later without changing anything about your UI It's important to note that setting `ssr:false` only disables _runtime server rendering_. React Router will still server render your root route at _build time_ to generate the `index.html` file. This is why your project still needs a dependency on `@react-router/node` and your routes need to be SSR-safe. That means you can't call `window` or other browser-only APIs during the initial render, even when server rendering is disabled. SPA Mode is a special form of "Pre-Rendering" that allows you to serve all paths in your application from the same HTML file. Please refer to the Pre-Rendering guide if you want to do more extensive pre-rendering. ## 1\. Disable Runtime Server Rendering Server rendering is enabled by default. Set the `ssr` flag to `false` in `react-router.config.ts` to disable it. import { type Config } from "@react-router/dev/config"; export default { ssr: false, } satisfies Config; With this set to false, the server build will no longer be generated. It's important to note that setting `ssr:false` only disables _runtime server rendering_. React Router will still server render your root route at _build time_ to generate the `index.html` file. This is why your project still needs a dependency on `@react-router/node` and your routes need to be SSR-safe. That means you can't call `window` or other browser-only APIs during the initial render, even when server rendering is disabled. ## 2\. Add a `HydrateFallback` and optional `loader` to your root route SPA Mode will generate an `index.html` file at build-time that you can serve as the entry point for your SPA. This will only render the root route so that it is capable of hydrating at runtime for any path in your application. To provide a better loading UI than an empty `<div>`, you can add a `HydrateFallback` component to your root route to render your loading UI into the `index.html` at build time. This way, it will be shown to users immediately while the SPA is loading/hydrating. import LoadingScreen from "./components/loading-screen"; export function Layout() { return <html>{/*...*/}</html>; } export function HydrateFallback() { return <LoadingScreen />; } export default function App() { return <Outlet />; } Because the root route is server-rendered at build time, you can also use a `loader` in your root route if you choose. This `loader` will be called at build time and the data will be available via the optional `HydrateFallback` `loaderData` prop. import { Route } from "./+types/root"; export async function loader() { return { version: await getVersion(), }; } export function HydrateFallback({ loaderData, }: Route.ComponentProps) { return ( <div> <h1>Loading version {loaderData.version}...</h1> <AwesomeSpinner /> </div> ); } You cannot include a `loader` in any other routes in your app when using SPA Mode unless you are pre-rendering those pages. ## 3\. Use client loaders and client actions With server rendering disabled, you can still use `clientLoader` and `clientAction` to manage route data and mutations. import { Route } from "./+types/some-route"; export async function clientLoader({ params, }: Route.ClientLoaderArgs) { let data = await fetch(`/some/api/stuff/${params.id}`); return data; } export async function clientAction({ request, }: Route.ClientActionArgs) { let formData = await request.formData(); return await processPayment(formData); } ## 4\. Direct all URLs to index.html After running `react-router build`, deploy the `build/client` directory to whatever static host you prefer. Common to deploying any SPA, you'll need to configure your host to direct all URLs to the `index.html` of the client build. Some hosts do this by default, but others don't. As an example, a host may support a `_redirects` file to do this: /* /index.html 200 If you're getting 404s at valid routes for your app, it's likely you need to configure your host. --- ## Page: https://reactrouter.com/how-to/status # Status Codes Set status codes from loaders and actions with `data`. // route('/projects/:projectId', './project.tsx') import type { Route } from "./+types/project"; import { data } from "react-router"; import { fakeDb } from "../db"; export async function action({ request, }: Route.ActionArgs) { let formData = await request.formData(); let title = formData.get("title"); if (!title) { return data( { message: "Invalid title" }, { status: 400 } ); } if (!projectExists(title)) { let project = await fakeDb.createProject({ title }); return data(project, { status: 201 }); } else { let project = await fakeDb.updateProject({ title }); // the default status code is 200, no need for `data` return project; } } See Form Validation for more information on rendering form errors like this. Another common status code is 404: // route('/projects/:projectId', './project.tsx') import type { Route } from "./+types/project"; import { data } from "react-router"; import { fakeDb } from "../db"; export async function loader({ params }: Route.ActionArgs) { let project = await fakeDb.getProject(params.id); if (!project) { // throw to ErrorBoundary throw data(null, { status: 404 }); } return project; } See the Error Boundaries for more information on thrown `data`. --- ## Page: https://reactrouter.com/how-to/suspense # Streaming with Suspense Streaming with React Suspense allows apps to speed up initial renders by deferring non-critical data and unblocking UI rendering. React Router supports React Suspense by returning promises from loaders and actions. ## 1\. Return a promise from loader React Router awaits route loaders before rendering route components. To unblock the loader for non-critical data, return the promise instead of awaiting it in the loader. import type { Route } from "./+types/my-route"; export async function loader({}: Route.LoaderArgs) { // note this is NOT awaited let nonCriticalData = new Promise((res) => setTimeout(() => res("non-critical"), 5000) ); let criticalData = await new Promise((res) => setTimeout(() => res("critical"), 300) ); return { nonCriticalData, criticalData }; } ## 2\. Render the fallback and resolved UI The promise will be available on `loaderData`, `<Await>` will await the promise and trigger `<Suspense>` to render the fallback UI. import * as React from "react"; import { Await } from "react-router"; // [previous code] export default function MyComponent({ loaderData, }: Route.ComponentProps) { let { criticalData, nonCriticalData } = loaderData; return ( <div> <h1>Streaming example</h1> <h2>Critical data value: {criticalData}</h2> <React.Suspense fallback={<div>Loading...</div>}> <Await resolve={nonCriticalData}> {(value) => <h3>Non critical value: {value}</h3>} </Await> </React.Suspense> </div> ); } ## With React 19 If you're experimenting with React 19, you can use `React.use` instead of `Await`, but you'll need to create a new component and pass the promise down to trigger the suspense fallback. <React.Suspense fallback={<div>Loading...</div>}> <NonCriticalUI p={nonCriticalData} /> </React.Suspense> function NonCriticalUI({ p }: { p: Promise<string> }) { let value = React.use(p); return <h3>Non critical value {value}</h3>; } --- ## Page: https://reactrouter.com/how-to/view-transitions # View Transitions Enable smooth animations between page transitions in your React Router applications using the View Transitions API. This feature allows you to create seamless visual transitions during client-side navigation. ## Basic View Transition ### 1\. Enable view transitions on navigation The simplest way to enable view transitions is by adding the `viewTransition` prop to your `Link`, `NavLink`, or `Form` components. This automatically wraps the navigation update in `document.startViewTransition()`. <Link to="/about" viewTransition> About </Link> Without any additional CSS, this provides a basic cross-fade animation between pages. For more information on using the View Transitions API, please refer to the "Smooth transitions with the View Transition API" guide from the Google Chrome team. ## Image Gallery Example Let's build an image gallery that demonstrates how to trigger and use view transitions. We'll create a list of images that expand into a detail view with smooth animations. ### 2\. Create the image gallery route import { NavLink } from "react-router"; export const images = [ "https://remix.run/blog-images/headers/the-future-is-now.jpg", "https://remix.run/blog-images/headers/waterfall.jpg", "https://remix.run/blog-images/headers/webpack.png", // ... more images ... ]; export default function ImageGalleryRoute() { return ( <div className="image-list"> <h1>Image List</h1> <div> {images.map((src, idx) => ( <NavLink key={src} to={`/image/${idx}`} viewTransition // Enable view transitions for this link > <p>Image Number {idx}</p> <img className="max-w-full contain-layout" src={src} /> </NavLink> ))} </div> </div> ); } ### 3\. Add transition styles Define view transition names and animations for elements that should transition smoothly between routes. /* Layout styles for the image grid */ .image-list > div { display: grid; grid-template-columns: repeat(4, 1fr); column-gap: 10px; } .image-list h1 { font-size: 2rem; font-weight: 600; } .image-list img { max-width: 100%; contain: layout; } .image-list p { width: fit-content; } /* Assign transition names to elements during navigation */ .image-list a.transitioning img { view-transition-name: image-expand; } .image-list a.transitioning p { view-transition-name: image-title; } ### 4\. Create the image detail route The detail view needs to use the same view transition names to create a seamless animation. import { Link } from "react-router"; import { images } from "./home"; import type { Route } from "./+types/image-details"; export default function ImageDetailsRoute({ params, }: Route.ComponentProps) { return ( <div className="image-detail"> <Link to="/" viewTransition> Back </Link> <h1>Image Number {params.id}</h1> <img src={images[Number(params.id)]} /> </div> ); } ### 5\. Add matching transition styles for the detail view /* Match transition names from the list view */ .image-detail h1 { font-size: 2rem; font-weight: 600; width: fit-content; view-transition-name: image-title; } .image-detail img { max-width: 100%; contain: layout; view-transition-name: image-expand; } ## Advanced Usage You can control view transitions more precisely using either render props or the `useViewTransitionState` hook. ### 1\. Using render props <NavLink to={`/image/${idx}`} viewTransition> {({ isTransitioning }) => ( <> <p style={{ viewTransitionName: isTransitioning ? "image-title" : "none", }} > Image Number {idx} </p> <img src={src} style={{ viewTransitionName: isTransitioning ? "image-expand" : "none", }} /> </> )} </NavLink> ### 2\. Using the `useViewTransitionState` hook function NavImage(props: { src: string; idx: number }) { const href = `/image/${props.idx}`; // Hook provides transition state for specific route const isTransitioning = useViewTransitionState(href); return ( <Link to={href} viewTransition> <p style={{ viewTransitionName: isTransitioning ? "image-title" : "none", }} > Image Number {props.idx} </p> <img src={props.src} style={{ viewTransitionName: isTransitioning ? "image-expand" : "none", }} /> </Link> ); } --- ## Page: https://reactrouter.com/explanation/code-splitting # Automatic Code Splitting When using React Router's framework features, your application is automatically code split to improve the performance of initial load times when users visit your application. ## Code Splitting by Route Consider this simple route config: import { type RouteConfig, route, } from "@react-router/dev/routes"; export default [ route("/contact", "./contact.tsx"), route("/about", "./about.tsx"), ] satisfies RouteConfig; Instead of bundling all routes into a single giant build, the modules referenced (`contact.tsx` and `about.tsx`) become entry points to the bundler. Because these entry points are coupled to URL segments, React Router knows just from a URL which bundles are needed in the browser, and more importantly, which are not. If the user visits `"/about"` then the bundles for `about.tsx` will be loaded but not `contact.tsx`. This ensures drastically reduces the JavaScript footprint for initial page loads and speeds up your application. ## Removal of Server Code Any server-only Route Module APIs will be removed from the bundles. Consider this route module: export async function loader() { return { message: "hello" }; } export async function action() { console.log(Date.now()); return { ok: true }; } export async function headers() { return { "Cache-Control": "max-age=300" }; } export default function Component({ loaderData }) { return <div>{loaderData.message}</div>; } After building for the browser, only the `Component` will still be in the bundle, so you can use server-only code in the other module exports. --- ## Page: https://reactrouter.com/explanation/hot-module-replacement # Hot Module Replacement Hot Module Replacement is a technique for updating modules in your app without needing to reload the page. It's a great developer experience, and React Router supports it when using Vite. HMR does its best to preserve browser state across updates. For example, let's say you have form within a modal and you fill out all the fields. As soon as you save any changes to the code, traditional live reload would hard refresh the page causing all of those fields to be reset. Every time you make a change, you'd have to open up the modal _again_ and fill out the form _again_. But with HMR, all of that state is preserved _across updates_. ## React Fast Refresh React already has mechanisms for updating the DOM via its virtual DOM in response to user interactions like clicking a button. Wouldn't it be great if React could handle updating the DOM in response to code changes too? That's exactly what React Fast Refresh is all about! Of course, React is all about components, not general JavaScript code, so React Fast Refresh only handles hot updates for exported React components. But React Fast Refresh does have some limitations that you should be aware of. ### Class Component State React Fast Refresh does not preserve state for class components. This includes higher-order components that internally return classes: export class ComponentA extends Component {} // β export const ComponentB = HOC(ComponentC); // β Won't work if HOC returns a class component export function ComponentD() {} // β export const ComponentE = () => {}; // β export default function ComponentF() {} // β ### Named Function Components Function components must be named, not anonymous, for React Fast Refresh to track changes: export default () => {}; // β export default function () {} // β const ComponentA = () => {}; export default ComponentA; // β export default function ComponentB() {} // β ### Supported Exports React Fast Refresh can only handle component exports. While React Router manages route exports like `action`, `headers`, `links`, `loader`, and `meta` for you, any user-defined exports will cause full reloads: // These exports are handled by the React Router Vite plugin // to be HMR-compatible export const meta = { title: "Home" }; // β export const links = [ { rel: "stylesheet", href: "style.css" }, ]; // β // These exports are removed by the React Router Vite plugin // so they never affect HMR export const headers = { "Cache-Control": "max-age=3600" }; // β export const loader = async () => {}; // β export const action = async () => {}; // β // This is not a route module export, nor a component export, // so it will cause a full reload for this route export const myValue = "some value"; // β export default function Route() {} // β π Routes probably shouldn't be exporting random values like that anyway. If you want to reuse values across routes, stick them in their own non-route module: export const myValue = "some value"; ### Changing Hooks React Fast Refresh cannot track changes for a component when hooks are being added or removed from it, causing full reloads just for the next render. After the hooks have been updated, changes should result in hot updates again. For example, if you add a `useState` to your component, you may lose that component's local state for the next render. Additionally, if you are destructuring a hook's return value, React Fast Refresh will not be able to preserve state for the component if the destructured key is removed or renamed. For example: export default function Component({ loaderData }) { const { pet } = useMyCustomHook(); return ( <div> <input /> <p>My dog's name is {pet.name}!</p> </div> ); } If you change the key `pet` to `dog`: export default function Component() { - const { pet } = useMyCustomHook(); + const { dog } = useMyCustomHook(); return ( <div> <input /> - <p>My dog's name is {pet.name}!</p> + <p>My dog's name is {dog.name}!</p> </div> ); } then React Fast Refresh will not be able to preserve state `<input />` β. ### Component Keys In some cases, React cannot distinguish between existing components being changed and new components being added. React needs `key`s to disambiguate these cases and track changes when sibling elements are modified. --- ## Page: https://reactrouter.com/explanation/progressive-enhancement # Progressive Enhancement > Progressive enhancement is a strategy in web design that puts emphasis on web content first, allowing everyone to access the basic content and functionality of a web page, whilst users with additional browser features or faster Internet access receive the enhanced version instead. \- Wikipedia When using React Router with Server-Side Rendering (the default in framework mode), you can automatically leverage the benefits of progressive enhancement. ## Why Progressive Enhancement Matters Coined in 2003 by Steven Champeon & Nick Finck, the phrase emerged during a time of varied CSS and JavaScript support across different browsers, with many users actually browsing the web with JavaScript disabled. Today, we are fortunate to develop for a much more consistent web and where the majority of users have JavaScript enabled. However, we still believe in the core principles of progressive enhancement in React Router. It leads to fast and resilient apps with simple development workflows. **Performance**: While it's easy to think that only 5% of your users have slow connections, the reality is that 100% of your users have slow connections 5% of the time. **Resilience**: Everybody has JavaScript disabled until it's loaded. **Simplicity**: Building your apps in a progressively enhanced way with React Router is actually simpler than building a traditional SPA. ## Performance Server rendering allows your app to do more things in parallel than a typical Single Page App (SPA), making the initial loading experience and subsequent navigations faster. Typical SPAs send a blank document and only start doing work when JavaScript has loaded: HTML |---| JavaScript |---------| Data |---------------| page rendered π A React Router app can start doing work the moment the request hits the server and stream the response so that the browser can start downloading JavaScript, other assets, and data in parallel: π first byte HTML |---|-----------| JavaScript |---------| Data |---------------| page rendered π ## Resilience and Accessibility While your users probably don't browse the web with JavaScript disabled, everybody uses the websites without JavaScript before it finishes loading. React Router embraces progressive enhancement by building on top of HTML, allowing you to build your app in a way that works without JavaScript, and then layer on JavaScript to enhance the experience. The simplest case is a `<Link to="/account">`. These render an `<a href="/account">` tag that works without JavaScript. When JavaScript loads, React Router will intercept clicks and handle the navigation with client side routing. This gives you more control over the UX instead of just spinning favicons in the browser tab--but it works either way. Now consider a simple add to cart button: 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> ); } Whether JavaScript has loaded or not doesn't matter, this button will add the product to the cart. When JavaScript loads, React Router will intercept the form submission and handle it client side. This allows you to add your own pending UI, or other client side behavior. ## Simplicity When you start to rely on basic features of the web like HTML and URLs, you will find that you reach for client side state and state management much less. Consider the button from before, with no fundamental change to the code, we can pepper in some client side behavior: 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> ); } This feature continues to work the very same as it did before when JavaScript is loading, but once JavaScript loads: * `useFetcher` no longer causes a navigation like `<Form>` does, so the user can stay on the same page and keep shopping * The app code determines the pending UI instead of spinning favicons in the browser It's not about building it two different waysβonce for JavaScript and once withoutβit's about building it in iterations. Start with the simplest version of the feature and ship it; then iterate to an enhanced user experience. Not only will the user get a progressively enhanced experience, but the app developer gets to "progressively enhance" the UI without changing the fundamental design of the feature. Another example where progressive enhancement leads to simplicity is with the URL. When you start with a URL, you don't need to worry about client side state management. You can just use the URL as the source of truth for the UI. export function SearchBox() { return ( <Form method="get" action="/search"> <input type="search" name="query" /> <SearchIcon /> </Form> ); } This component doesn't need any state management. It just renders a form that submits to `/search`. When JavaScript loads, React Router will intercept the form submission and handle it client side. Here's the next iteration: 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> ); } No fundamental change in architecture, simply a progressive enhancement for both the user and the code. See also: State Management --- ## Page: https://reactrouter.com/explanation/race-conditions # Race Conditions While impossible to eliminate every possible race condition in your application, React Router automatically handles the most common race conditions found in web user interfaces. ## Browser Behavior React Router's handling of network concurrency is heavily inspired by the behavior of web browsers when processing documents. Consider clicking a link to a new document, and then clicking a different link before the new page has finished loading. The browser will: 1. cancel the first request 2. immediately process the new navigation The same behavior applies to form submissions. When a pending form submission is interrupted by a new one, the first is canceled and the new submission is immediately processed. ## React Router Behavior Like the browser, interrupted navigations with links and form submissions will cancel in flight data requests and immediately process the new event. Fetchers are a bit more nuanced since they are not singleton events like navigation. Fetchers can't interrupt other fetcher instances, but they can interrupt themselves and the behavior is the same as everything else: cancel the interrupted request and immediately process the new one. Fetchers do, however, interact with each other when it comes to revalidation. After a fetcher's action request returns to the browser, a revalidation for all page data is sent. This means multiple revalidation requests can be in-flight at the same time. React Router will commit all "fresh" revalidation responses and cancel any stale requests. A stale request is any request that started _earlier_ than one that has returned. This management of the network prevents the most common UI bugs caused by network race conditions. Since networks are unpredictable, and your server still processes these cancelled requests, your backend may still experience race conditions and have potential data integrity issues. These risks are the same risks as using default browser behavior with plain HTML `<forms>`, which we consider to be low, and outside the scope of React Router. ## Practical Benefits Consider building a type-ahead combobox. As the user types, you send a request to the server. As they type each new character you send a new request. It's important to not show the user results for a value that's not in the text field anymore. When using a fetcher, this is automatically managed for you. Consider this pseudo-code: // route("/city-search", "./search-cities.ts") 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) => // submit the form onChange to get the list of cities fetcher.submit(event.target.form) } /> {fetcher.data ? ( <ComboboxPopover className="shadow-popup"> {fetcher.data.length > 0 ? ( <ComboboxList> {fetcher.data.map((city) => ( <ComboboxOption key={city.id} value={city.name} /> ))} </ComboboxList> ) : ( <span>No results found</span> )} </ComboboxPopover> ) : null} </Combobox> </fetcher.Form> ); } Calls to `fetcher.submit` will cancel pending requests on that fetcher automatically. This ensures you never show the user results for a request for a different input value. --- ## Page: https://reactrouter.com/explanation/sessions-and-cookies # Sessions and Cookies ## Sessions Sessions are an important part of websites that allow the server to identify requests coming from the same person, especially when it comes to server-side form validation or when JavaScript is not on the page. Sessions are a fundamental building block of many sites that let users "log in", including social, e-commerce, business, and educational websites. When using React Router as your framework, sessions are managed on a per-route basis (rather than something like express middleware) in your `loader` and `action` methods using a "session storage" object (that implements the `SessionStorage` interface). Session storage understands how to parse and generate cookies, and how to store session data in a database or filesystem. ### Using Sessions This is an example of a cookie session storage: import { createCookieSessionStorage } from "react-router"; type SessionData = { userId: string; }; type SessionFlashData = { error: string; }; const { getSession, commitSession, destroySession } = createCookieSessionStorage<SessionData, SessionFlashData>( { // a Cookie from `createCookie` or the CookieOptions to create one cookie: { name: "__session", // all of these are optional domain: "reactrouter.com", // Expires can also be set (although maxAge overrides it when used in combination). // Note that this method is NOT recommended as `new Date` creates only one date on each server deployment, not a dynamic date in the future! // // expires: new Date(Date.now() + 60_000), httpOnly: true, maxAge: 60, path: "/", sameSite: "lax", secrets: ["s3cret1"], secure: true, }, } ); export { getSession, commitSession, destroySession }; We recommend setting up your session storage object in `app/sessions.server.ts` so all routes that need to access session data can import from the same spot. The input/output to a session storage object are HTTP cookies. `getSession()` retrieves the current session from the incoming request's `Cookie` header, and `commitSession()`/`destroySession()` provide the `Set-Cookie` header for the outgoing response. You'll use methods to get access to sessions in your `loader` and `action` functions. After retrieving a session with `getSession`, the returned session object has a handful of methods and properties: export async function action({ request, }: ActionFunctionArgs) { const session = await getSession( request.headers.get("Cookie") ); session.get("foo"); session.has("bar"); // etc. } See the Session API for all methods available on the session object. ### Login form example A login form might look something like this: import { data, redirect } from "react-router"; import type { Route } from "./+types/login"; import { getSession, commitSession, } from "../sessions.server"; export async function loader({ request, }: Route.LoaderArgs) { const session = await getSession( request.headers.get("Cookie") ); if (session.has("userId")) { // Redirect to the home page if they are already signed in. return redirect("/"); } return data( { error: session.get("error") }, { headers: { "Set-Cookie": await commitSession(session), }, } ); } export async function action({ request, }: Route.ActionArgs) { const session = await getSession( request.headers.get("Cookie") ); const form = await request.formData(); const username = form.get("username"); const password = form.get("password"); const userId = await validateCredentials( username, password ); if (userId == null) { session.flash("error", "Invalid username/password"); // Redirect back to the login page with errors. return redirect("/login", { headers: { "Set-Cookie": await commitSession(session), }, }); } session.set("userId", userId); // Login succeeded, send them to the home page. return redirect("/", { headers: { "Set-Cookie": await commitSession(session), }, }); } export default function Login({ loaderData, }: Route.ComponentProps) { const { error } = loaderData; return ( <div> {error ? <div className="error">{error}</div> : null} <form method="POST"> <div> <p>Please sign in</p> </div> <label> Username: <input type="text" name="username" /> </label> <label> Password:{" "} <input type="password" name="password" /> </label> </form> </div> ); } And then a logout form might look something like this: import { getSession, destroySession, } from "../sessions.server"; import type { Route } from "./+types/logout"; export async function action({ request, }: Route.ActionArgs) { const session = await getSession( request.headers.get("Cookie") ); return redirect("/login", { headers: { "Set-Cookie": await destroySession(session), }, }); } export default function LogoutRoute() { return ( <> <p>Are you sure you want to log out?</p> <Form method="post"> <button>Logout</button> </Form> <Link to="/">Never mind</Link> </> ); } It's important that you logout (or perform any mutation for that matter) in an `action` and not a `loader`. Otherwise you open your users to Cross-Site Request Forgery attacks. ### Session Gotchas Because of nested routes, multiple loaders can be called to construct a single page. When using `session.flash()` or `session.unset()`, you need to be sure no other loaders in the request are going to want to read that, otherwise you'll get race conditions. Typically if you're using flash, you'll want to have a single loader read it, if another loader wants a flash message, use a different key for that loader. ### Creating custom session storage React Router makes it easy to store sessions in your own database if needed. The `createSessionStorage()` API requires a `cookie` (for options for creating a cookie, see cookies) and a set of create, read, update, and delete (CRUD) methods for managing the session data. The cookie is used to persist the session ID. * `createData` will be called from `commitSession` on the initial session creation when no session ID exists in the cookie * `readData` will be called from `getSession` when a session ID exists in the cookie * `updateData` will be called from `commitSession` when a session ID already exists in the cookie * `deleteData` is called from `destroySession` The following example shows how you could do this using a generic database client: import { createSessionStorage } from "react-router"; function createDatabaseSessionStorage({ cookie, host, port, }) { // Configure your database client... const db = createDatabaseClient(host, port); return createSessionStorage({ cookie, async createData(data, expires) { // `expires` is a Date after which the data should be considered // invalid. You could use it to invalidate the data somehow or // automatically purge this record from your database. const id = await db.insert(data); return id; }, async readData(id) { return (await db.select(id)) || null; }, async updateData(id, data, expires) { await db.update(id, data); }, async deleteData(id) { await db.delete(id); }, }); } And then you can use it like this: const { getSession, commitSession, destroySession } = createDatabaseSessionStorage({ host: "localhost", port: 1234, cookie: { name: "__session", sameSite: "lax", }, }); The `expires` argument to `createData` and `updateData` is the same `Date` at which the cookie itself expires and is no longer valid. You can use this information to automatically purge the session record from your database to save on space, or to ensure that you do not otherwise return any data for old, expired cookies. ### Additional session utils There are also several other session utilities available if you need them: * `isSession` * `createMemorySessionStorage` * `createSession` (custom storage) * `createFileSessionStorage` (node) * `createWorkersKVSessionStorage` (Cloudflare Workers) * `createArcTableSessionStorage` (architect, Amazon DynamoDB) ## Cookies A cookie is a small piece of information that your server sends someone in a HTTP response that their browser will send back on subsequent requests. This technique is a fundamental building block of many interactive websites that adds state so you can build authentication (see sessions), shopping carts, user preferences, and many other features that require remembering who is "logged in". React Router's `Cookie` interface provides a logical, reusable container for cookie metadata. ### Using cookies While you may create these cookies manually, it is more common to use a session storage. In React Router, you will typically work with cookies in your `loader` and/or `action` functions, since those are the places where you need to read and write data. Let's say you have a banner on your e-commerce site that prompts users to check out the items you currently have on sale. The banner spans the top of your homepage, and includes a button on the side that allows the user to dismiss the banner so they don't see it for at least another week. First, create a cookie: import { createCookie } from "react-router"; export const userPrefs = createCookie("user-prefs", { maxAge: 604_800, // one week }); Then, you can `import` the cookie and use it in your `loader` and/or `action`. The `loader` in this case just checks the value of the user preference so you can use it in your component for deciding whether to render the banner. When the button is clicked, the `<form>` calls the `action` on the server and reloads the page without the banner. ### User preferences example import { Link, Form, redirect } from "react-router"; import type { Route } from "./+types/home"; import { userPrefs } from "../cookies.server"; export async function loader({ request, }: Route.LoaderArgs) { const cookieHeader = request.headers.get("Cookie"); const cookie = (await userPrefs.parse(cookieHeader)) || {}; return { showBanner: cookie.showBanner }; } export async function action({ request, }: Route.ActionArgs) { const cookieHeader = request.headers.get("Cookie"); const cookie = (await userPrefs.parse(cookieHeader)) || {}; const bodyParams = await request.formData(); if (bodyParams.get("bannerVisibility") === "hidden") { cookie.showBanner = false; } return redirect("/", { headers: { "Set-Cookie": await userPrefs.serialize(cookie), }, }); } export default function Home({ loaderData, }: Route.ComponentProps) { return ( <div> {loaderData.showBanner ? ( <div> <Link to="/sale">Don't miss our sale!</Link> <Form method="post"> <input type="hidden" name="bannerVisibility" value="hidden" /> <button type="submit">Hide</button> </Form> </div> ) : null} <h1>Welcome!</h1> </div> ); } ### Cookie attributes Cookies have several attributes that control when they expire, how they are accessed, and where they are sent. Any of these attributes may be specified either in `createCookie(name, options)`, or during `serialize()` when the `Set-Cookie` header is generated. const cookie = createCookie("user-prefs", { // These are defaults for this cookie. path: "/", sameSite: "lax", httpOnly: true, secure: true, expires: new Date(Date.now() + 60_000), maxAge: 60, }); // You can either use the defaults: cookie.serialize(userPrefs); // Or override individual ones as needed: cookie.serialize(userPrefs, { sameSite: "strict" }); Please read more info about these attributes to get a better understanding of what they do. ### Signing cookies It is possible to sign a cookie to automatically verify its contents when it is received. Since it's relatively easy to spoof HTTP headers, this is a good idea for any information that you do not want someone to be able to fake, like authentication information (see sessions). To sign a cookie, provide one or more `secrets` when you first create the cookie: const cookie = createCookie("user-prefs", { secrets: ["s3cret1"], }); Cookies that have one or more `secrets` will be stored and verified in a way that ensures the cookie's integrity. Secrets may be rotated by adding new secrets to the front of the `secrets` array. Cookies that have been signed with old secrets will still be decoded successfully in `cookie.parse()`, and the newest secret (the first one in the array) will always be used to sign outgoing cookies created in `cookie.serialize()`. export const cookie = createCookie("user-prefs", { secrets: ["n3wsecr3t", "olds3cret"], }); import { data } from "react-router"; import { cookie } from "../cookies.server"; import type { Route } from "./+types/my-route"; export async function loader({ request, }: Route.LoaderArgs) { const oldCookie = request.headers.get("Cookie"); // oldCookie may have been signed with "olds3cret", but still parses ok const value = await cookie.parse(oldCookie); return data("...", { headers: { // Set-Cookie is signed with "n3wsecr3t" "Set-Cookie": await cookie.serialize(value), }, }); } ### Additional cookie utils There are also several other cookie utilities available if you need them: * `isCookie` * `createCookie` To learn more about each attribute, please see the MDN Set-Cookie docs. --- ## Page: https://reactrouter.com/explanation/special-files # Special Files There are a few special files that React Router looks for in your project. Not all of these files are required ## react-router.config.ts **This file is optional** The config file is used to configure certain aspects of your app, such as whether you are using server-side rendering, where certain directories are located, and more. import type { Config } from "@react-router/dev/config"; export default { // Config options... } satisfies Config; See the details on react-router config API for more information. ## root.tsx **This file is required** The "root" route (`app/root.tsx`) is the only _required_ route in your React Router application because it is the parent to all routes in your `routes/` directory and is in charge of rendering the root `<html>` document. Because the root route manages your document, it is the proper place to render a handful of "document-level" components React Router provides. These components are to be used once inside your root route and they include everything React Router figured out or built in order for your page to render properly. import type { LinksFunction } from "react-router"; import { Links, Meta, Outlet, Scripts, ScrollRestoration, } from "react-router"; import "./global-styles.css"; export default function App() { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> {/* All `meta` exports on all routes will render here */} <Meta /> {/* All `link` exports on all routes will render here */} <Links /> </head> <body> {/* Child routes render here */} <Outlet /> {/* Manages scroll position for client-side transitions */} {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */} <ScrollRestoration /> {/* Script tags go here */} {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */} <Scripts /> </body> </html> ); } ### Layout export The root route supports all route module exports. The root route also supports an additional optional `Layout` export. The `Layout` component serves 2 purposes: 1. Avoid duplicating your document's "app shell" across your root component, `HydrateFallback`, and `ErrorBoundary` 2. Prevent React from re-mounting your app shell elements when switching between the root component/`HydrateFallback`/`ErrorBoundary` which can cause a FOUC if React removes and re-adds `<link rel="stylesheet">` tags from your `<Links>` component. export function Layout({ children }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {/* children will be the root Component, ErrorBoundary, or HydrateFallback */} {children} <Scripts /> <ScrollRestoration /> </body> </html> ); } export default function App() { return <Outlet />; } export function ErrorBoundary() {} **A note on `useLoaderData`in the `Layout` Component** `useLoaderData` is not permitted to be used in `ErrorBoundary` components because it is intended for the happy-path route rendering, and its typings have a built-in assumption that the `loader` ran successfully and returned something. That assumption doesn't hold in an `ErrorBoundary` because it could have been the `loader` that threw and triggered the boundary! In order to access loader data in `ErrorBoundary`'s, you can use `useRouteLoaderData` which accounts for the loader data potentially being `undefined`. Because your `Layout` component is used in both success and error flows, this same restriction holds. If you need to fork logic in your `Layout` depending on if it was a successful request or not, you can use `useRouteLoaderData("root")` and `useRouteError()`. Because your `<Layout>` component is used for rendering the `ErrorBoundary`, you should be _very defensive_ to ensure that you can render your `ErrorBoundary` without encountering any render errors. If your `Layout` throws another error trying to render the boundary, then it can't be used and your UI will fall back to the very minimal built-in default `ErrorBoundary`. export function Layout({ children, }: { children: React.ReactNode; }) { const data = useRouteLoaderData("root"); const error = useRouteError(); return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> <style dangerouslySetInnerHTML={{ __html: ` :root { --themeVar: ${ data?.themeVar || defaultThemeVar } } `, }} /> </head> <body> {data ? ( <Analytics token={data.analyticsToken} /> ) : null} {children} <ScrollRestoration /> <Scripts /> </body> </html> ); } ## routes.ts **This file is required** The `routes.ts` file is used to configure which url patterns are matched to which route modules. import { type RouteConfig, route, } from "@react-router/dev/routes"; export default [ route("some/path", "./some/file.tsx"), // pattern ^ ^ module file ] satisfies RouteConfig; See the routing guide for more information. ## entry.client.tsx **This file is optional** By default, React Router will handle hydrating your app on the client for you. You can reveal the default entry client file with the following: react-router reveal This file is the entry point for the browser and is responsible for hydrating the markup generated by the server in your server entry module, however you can also initialize any other client-side code here. import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; startTransition(() => { hydrateRoot( document, <StrictMode> <HydratedRouter /> </StrictMode> ); }); This is the first piece of code that runs in the browser. You can initialize client side libraries, add client only providers, etc. ## entry.server.tsx **This file is optional** By default, React Router will handle generating the HTTP Response for you. You can reveal the default entry server file with the following: react-router reveal The `default` export of this module is a function that lets you create the response, including HTTP status, headers, and HTML, giving you full control over the way the markup is generated and sent to the client. This module should render the markup for the current page using a `<ServerRouter>` element with the `context` and `url` for the current request. This markup will (optionally) be re-hydrated once JavaScript loads in the browser using the client entry module. ### `streamTimeout` If you are streaming responses, you can export an optional `streamTimeout` value (in milliseconds) that will control the amount of time the server will wait for streamed promises to settle before rejecting outstanding promises them and closing the stream. It's recommended to decouple this value from the timeout in which you abort the React renderer. You should always set the React rendering timeout to a higher value so it has time to stream down the underlying rejections from your `streamTimeout`. // Reject all pending promises from handler functions after 10 seconds export const streamTimeout = 10000; export default function handleRequest(...) { return new Promise((resolve, reject) => { // ... const { pipe, abort } = renderToPipeableStream( <ServerRouter context={routerContext} url={request.url} />, { /* ... */ } ); // Abort the streaming render pass after 11 seconds to allow the rejected // boundaries to be flushed setTimeout(abort, streamTimeout + 1000); }); } ### `handleDataRequest` You can export an optional `handleDataRequest` function that will allow you to modify the response of a data request. These are the requests that do not render HTML, but rather return the loader and action data to the browser once client-side hydration has occurred. export function handleDataRequest( response: Response, { request, params, context, }: LoaderFunctionArgs | ActionFunctionArgs ) { response.headers.set("X-Custom-Header", "value"); return response; } ### `handleError` By default, React Router will log encountered server-side errors to the console. If you'd like more control over the logging, or would like to also report these errors to an external service, then you can export an optional `handleError` function which will give you control (and will disable the built-in error logging). export function handleError( error: unknown, { request, params, context, }: LoaderFunctionArgs | ActionFunctionArgs ) { if (!request.signal.aborted) { sendErrorToErrorReportingService(error); console.error(formatErrorForJsonLogging(error)); } } _Note that you generally want to avoid logging when the request was aborted, since React Router's cancellation and race-condition handling can cause a lot of requests to be aborted._ ### Streaming Rendering Errors When you are streaming your HTML responses via `renderToPipeableStream` or `renderToReadableStream`, your own `handleError` implementation will only handle errors encountered during the initial shell render. If you encounter a rendering error during subsequent streamed rendering you will need to handle these errors manually since the React Router server has already sent the Response by that point. For `renderToPipeableStream`, you can handle these errors in the `onError` callback function. You will need to toggle a boolean in `onShellReady` so you know if the error was a shell rendering error (and can be ignored) or an async For an example, please refer to the default `entry.server.tsx` for Node. **Thrown Responses** Note that this does not handle thrown `Response` instances from your `loader`/`action` functions. The intention of this handler is to find bugs in your code which result in unexpected thrown errors. If you are detecting a scenario and throwing a 401/404/etc. `Response` in your `loader`/`action` then it's an expected flow that is handled by your code. If you also wish to log, or send those to an external service, that should be done at the time you throw the response. ## `.server` modules While not strictly necessary, `.server` modules are a good way to explicitly mark entire modules as server-only. The build will fail if any code in a `.server` file or `.server` directory accidentally ends up in the client module graph. app βββ .server π marks all files in this directory as server-only β βββ auth.ts β βββ db.ts βββ cms.server.ts π marks this file as server-only βββ root.tsx βββ routes.ts `.server` modules must be within your app directory. Refer to the Route Module section in the sidebar for more information. ## `.client` modules While uncommon, you may have a file or dependency that uses module side effects in the browser. You can use `*.client.ts` on file names or nest files within `.client` directories to force them out of server bundles. // this would break the server export const supportsVibrationAPI = "vibrate" in window.navigator; Note that values exported from this module will all be `undefined` on the server, so the only places to use them are in `useEffect` and user events like click handlers. import { supportsVibrationAPI } from "./feature-check.client.ts"; console.log(supportsVibrationAPI); // server: undefined // client: true | false --- ## Page: https://reactrouter.com/explanation/state-management # State Management State management in React typically involves maintaining a synchronized cache of server data on the client side. However, when using React Router as your framework, most of the traditional caching solutions become redundant because of how it inherently handles data synchronization. ## Understanding State Management in React In a typical React context, when we refer to "state management", we're primarily discussing how we synchronize server state with the client. A more apt term could be "cache management" because the server is the source of truth and the client state is mostly functioning as a cache. Popular caching solutions in React include: * **Redux:** A predictable state container for JavaScript apps. * **React Query:** Hooks for fetching, caching, and updating asynchronous data in React. * **Apollo:** A comprehensive state management library for JavaScript that integrates with GraphQL. In certain scenarios, using these libraries may be warranted. However, with React Router's unique server-focused approach, their utility becomes less prevalent. In fact, most React Router applications forgo them entirely. ## How React Router Simplifies State React Router seamlessly bridges the gap between the backend and frontend via mechanisms like loaders, actions, and forms with automatic synchronization through revalidation. This offers developers the ability to directly use server state within components without managing a cache, the network communication, or data revalidation, making most client-side caching redundant. Here's why using typical React state patterns might be an anti-pattern in React Router: 1. **Network-related State:** If your React state is managing anything related to the networkβsuch as data from loaders, pending form submissions, or navigational statesβit's likely that you're managing state that React Router already manages: * **`useNavigation`**: This hook gives you access to `navigation.state`, `navigation.formData`, `navigation.location`, etc. * **`useFetcher`**: This facilitates interaction with `fetcher.state`, `fetcher.formData`, `fetcher.data` etc. * **`loaderData`**: Access the data for a route. * **`actionData`**: Access the data from the latest action. 2. **Storing Data in React Router:** A lot of data that developers might be tempted to store in React state has a more natural home in React Router, such as: * **URL Search Params:** Parameters within the URL that hold state. * **Cookies:** Small pieces of data stored on the user's device. * **Server Sessions:** Server-managed user sessions. * **Server Caches:** Cached data on the server side for quicker retrieval. 3. **Performance Considerations:** At times, client state is leveraged to avoid redundant data fetching. With React Router, you can use the `Cache-Control` headers within `loader`s, allowing you to tap into the browser's native cache. However, this approach has its limitations and should be used judiciously. It's usually more beneficial to optimize backend queries or implement a server cache. This is because such changes benefit all users and do away with the need for individual browser caches. As a developer transitioning to React Router, it's essential to recognize and embrace its inherent efficiencies rather than applying traditional React patterns. React Router offers a streamlined solution to state management leading to less code, fresh data, and no state synchronization bugs. ## Examples ### Network Related State For examples on using React Router's internal state to manage network related state, refer to Pending UI. ### URL Search Params Consider a UI that lets the user customize between list view or detail view. Your instinct might be to reach for React state: export function List() { const [view, setView] = useState("list"); return ( <div> <div> <button onClick={() => setView("list")}> View as List </button> <button onClick={() => setView("details")}> View with Details </button> </div> {view === "list" ? <ListView /> : <DetailView />} </div> ); } Now consider you want the URL to update when the user changes the view. Note the state synchronization: import { useNavigate, useSearchParams } from "react-router"; export function List() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [view, setView] = useState( searchParams.get("view") || "list" ); return ( <div> <div> <button onClick={() => { setView("list"); navigate(`?view=list`); }} > View as List </button> <button onClick={() => { setView("details"); navigate(`?view=details`); }} > View with Details </button> </div> {view === "list" ? <ListView /> : <DetailView />} </div> ); } Instead of synchronizing state, you can simply read and set the state in the URL directly with boring old HTML forms: 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> ); } ### Persistent UI State Consider a UI that toggles a sidebar's visibility. We have three ways to handle the state: 1. React state 2. Browser local storage 3. Cookies In this discussion, we'll break down the trade-offs associated with each method. #### React State React state provides a simple solution for temporary state storage. **Pros**: * **Simple**: Easy to implement and understand. * **Encapsulated**: State is scoped to the component. **Cons**: * **Transient**: Doesn't survive page refreshes, returning to the page later, or unmounting and remounting the component. **Implementation**: function Sidebar() { const [isOpen, setIsOpen] = useState(false); return ( <div> <button onClick={() => setIsOpen((open) => !open)}> {isOpen ? "Close" : "Open"} </button> <aside hidden={!isOpen}> <Outlet /> </aside> </div> ); } #### Local Storage To persist state beyond the component lifecycle, browser local storage is a step-up. See our doc on Client Data for more advanced examples. **Pros**: * **Persistent**: Maintains state across page refreshes and component mounts/unmounts. * **Encapsulated**: State is scoped to the component. **Cons**: * **Requires Synchronization**: React components must sync up with local storage to initialize and save the current state. * **Server Rendering Limitation**: The `window` and `localStorage` objects are not accessible during server-side rendering, so state must be initialized in the browser with an effect. * **UI Flickering**: On initial page loads, the state in local storage may not match what was rendered by the server and the UI will flicker when JavaScript loads. **Implementation**: function Sidebar() { const [isOpen, setIsOpen] = useState(false); // synchronize initially useLayoutEffect(() => { const isOpen = window.localStorage.getItem("sidebar"); setIsOpen(isOpen); }, []); // synchronize on change useEffect(() => { window.localStorage.setItem("sidebar", isOpen); }, [isOpen]); return ( <div> <button onClick={() => setIsOpen((open) => !open)}> {isOpen ? "Close" : "Open"} </button> <aside hidden={!isOpen}> <Outlet /> </aside> </div> ); } In this approach, state must be initialized within an effect. This is crucial to avoid complications during server-side rendering. Directly initializing the React state from `localStorage` will cause errors since `window.localStorage` is unavailable during server rendering. function Sidebar() { const [isOpen, setIsOpen] = useState( // error: window is not defined window.localStorage.getItem("sidebar") ); // ... } By initializing the state within an effect, there's potential for a mismatch between the server-rendered state and the state stored in local storage. This discrepancy will lead to brief UI flickering shortly after the page renders and should be avoided. #### Cookies Cookies offer a comprehensive solution for this use case. However, this method introduces added preliminary setup before making the state accessible within the component. **Pros**: * **Server Rendering**: State is available on the server for rendering and even for server actions. * **Single Source of Truth**: Eliminates state synchronization hassles. * **Persistence**: Maintains state across page loads and component mounts/unmounts. State can even persist across devices if you switch to a database-backed session. * **Progressive Enhancement**: Functions even before JavaScript loads. **Cons**: * **Boilerplate**: Requires more code because of the network. * **Exposed**: The state is not encapsulated to a single component, other parts of the app must be aware of the cookie. **Implementation**: First we'll need to create a cookie object: import { createCookie } from "react-router"; export const prefs = createCookie("prefs"); Next we set up the server action and loader to read and write the cookie: import { data, Outlet } from "react-router"; import type { Route } from "./+types/sidebar"; import { prefs } from "./prefs-cookie"; // read the state from the cookie export async function loader({ request, }: Route.LoaderArgs) { const cookieHeader = request.headers.get("Cookie"); const cookie = (await prefs.parse(cookieHeader)) || {}; return data({ sidebarIsOpen: cookie.sidebarIsOpen }); } // write the state to the cookie export async function action({ request, }: Route.ActionArgs) { const cookieHeader = request.headers.get("Cookie"); const cookie = (await prefs.parse(cookieHeader)) || {}; const formData = await request.formData(); const isOpen = formData.get("sidebar") === "open"; cookie.sidebarIsOpen = isOpen; return data(isOpen, { headers: { "Set-Cookie": await prefs.serialize(cookie), }, }); } After the server code is set up, we can use the cookie state in our UI: function Sidebar({ loaderData }: Route.ComponentProps) { const fetcher = useFetcher(); let { sidebarIsOpen } = loaderData; // use optimistic UI to immediately change the UI state if (fetcher.formData?.has("sidebar")) { sidebarIsOpen = fetcher.formData.get("sidebar") === "open"; } return ( <div> <fetcher.Form method="post"> <button name="sidebar" value={sidebarIsOpen ? "closed" : "open"} > {sidebarIsOpen ? "Close" : "Open"} </button> </fetcher.Form> <aside hidden={!sidebarIsOpen}> <Outlet /> </aside> </div> ); } While this is certainly more code that touches more of the application to account for the network requests and responses, the UX is greatly improved. Additionally, state comes from a single source of truth without any state synchronization required. In summary, each of the discussed methods offers a unique set of benefits and challenges: * **React state**: Offers simple but transient state management. * **Local Storage**: Provides persistence but with synchronization requirements and UI flickering. * **Cookies**: Delivers robust, persistent state management at the cost of added boilerplate. None of these are wrong, but if you want to persist the state across visits, cookies offer the best user experience. ### Form Validation and Action Data Client-side validation can augment the user experience, but similar enhancements can be achieved by leaning more towards server-side processing and letting it handle the complexities. The following example illustrates the inherent complexities of managing network state, coordinating state from the server, and implementing validation redundantly on both the client and server sides. It's just for illustration, so forgive any obvious bugs or problems you find. export function Signup() { // A multitude of React State declarations const [isSubmitting, setIsSubmitting] = useState(false); const [userName, setUserName] = useState(""); const [userNameError, setUserNameError] = useState(null); const [password, setPassword] = useState(null); const [passwordError, setPasswordError] = useState(""); // Replicating server-side logic in the client function validateForm() { setUserNameError(null); setPasswordError(null); const errors = validateSignupForm(userName, password); if (errors) { if (errors.userName) { setUserNameError(errors.userName); } if (errors.password) { setPasswordError(errors.password); } } return Boolean(errors); } // Manual network interaction handling async function handleSubmit() { if (validateForm()) { setSubmitting(true); const res = await postJSON("/api/signup", { userName, password, }); const json = await res.json(); setIsSubmitting(false); // Server state synchronization to the client if (json.errors) { if (json.errors.userName) { setUserNameError(json.errors.userName); } if (json.errors.password) { setPasswordError(json.errors.password); } } } } return ( <form onSubmit={(event) => { event.preventDefault(); handleSubmit(); }} > <p> <input type="text" name="username" value={userName} onChange={() => { // Synchronizing form state for the fetch setUserName(event.target.value); }} /> {userNameError ? <i>{userNameError}</i> : null} </p> <p> <input type="password" name="password" onChange={(event) => { // Synchronizing form state for the fetch setPassword(event.target.value); }} /> {passwordError ? <i>{passwordError}</i> : null} </p> <button disabled={isSubmitting} type="submit"> Sign Up </button> {isSubmitting ? <BusyIndicator /> : null} </form> ); } The backend endpoint, `/api/signup`, also performs validation and sends error feedback. Note that some essential validation, like detecting duplicate usernames, can only be done server-side using information the client doesn't have access to. export async function signupHandler(request: Request) { const errors = await validateSignupRequest(request); if (errors) { return { ok: false, errors: errors }; } await signupUser(request); return { ok: true, errors: null }; } Now, let's contrast this with a React Router-based implementation. The action remains consistent, but the component is vastly simplified due to the direct utilization of server state via `actionData`, and leveraging the network state that React Router inherently manages. import { useNavigation } from "react-router"; import type { Route } from "./+types/signup"; export async function action({ request, }: ActionFunctionArgs) { const errors = await validateSignupRequest(request); if (errors) { return { ok: false, errors: errors }; } await signupUser(request); return { ok: true, errors: null }; } export function Signup({ actionData, }: Route.ComponentProps) { const navigation = useNavigation(); const userNameError = actionData?.errors?.userName; const passwordError = actionData?.errors?.password; const isSubmitting = navigation.formAction === "/signup"; return ( <Form method="post"> <p> <input type="text" name="username" /> {userNameError ? <i>{userNameError}</i> : null} </p> <p> <input type="password" name="password" /> {passwordError ? <i>{passwordError}</i> : null} </p> <button disabled={isSubmitting} type="submit"> Sign Up </button> {isSubmitting ? <BusyIndicator /> : null} </Form> ); } The extensive state management from our previous example is distilled into just three code lines. We eliminate the necessity for React state, change event listeners, submit handlers, and state management libraries for such network interactions. Direct access to the server state is made possible through `actionData`, and network state through `useNavigation` (or `useFetcher`). As bonus party trick, the form is functional even before JavaScript loads (see Progressive Enhancement). Instead of React Router managing the network operations, the default browser behaviors step in. If you ever find yourself entangled in managing and synchronizing state for network operations, React Router likely offers a more elegant solution. --- ## Page: https://reactrouter.com/explanation/type-safety # Type Safety If you haven't done so already, check out our guide for setting up type safety in a new project. React Router generates types for each route in your app to provide type safety for the route module exports. For example, let's say you have a `products/:id` route configured: import { type RouteConfig, route, } from "@react-router/dev/routes"; export default [ route("products/:id", "./routes/product.tsx"), ] satisfies RouteConfig; You can import route-specific types like so: import type { Route } from "./+types/product"; // types generated for this route π export function loader({ params }: Route.LoaderArgs) { // π { id: string } return { planet: `world #${params.id}` }; } export default function Component({ loaderData, // π { planet: string } }: Route.ComponentProps) { return <h1>Hello, {loaderData.planet}!</h1>; } ## How it works React Router's type generation executes your route config (`app/routes.ts` by default) to determine the routes for your app. It then generates a `+types/<route file>.d.ts` for each route within a special `.react-router/types/` directory. With `rootDirs` configured, TypeScript can import these generated files as if they were right next to their corresponding route modules. For a deeper dive into some of the design decisions, check out our type inference decision doc. ## `typegen` command You can manually generate types with the `typegen` command: react-router typegen The following types are generated for each route: * `LoaderArgs` * `ClientLoaderArgs` * `ActionArgs` * `ClientActionArgs` * `HydrateFallbackProps` * `ComponentProps` (for the `default` export) * `ErrorBoundaryProps` ### \--watch If you run `react-router dev` β or if your custom server calls `vite.createServer` β then React Router's Vite plugin is already generating up-to-date types for you. But if you really need to run type generation on its own, you can also use `--watch` to automatically regenerate types as files change: react-router typegen --watch