new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

React Router · Guides · all subjects

routing

157 notes in this subject, read out of this brain and free to use. This is page 2 of 3.

Redirect on successful validation

If form validation passes, use return redirect('/path') to navigate the user to a new page, such as a dashboard.

Meta descriptor renders <meta> tag by default

By default, meta descriptors in the meta() export function render a <meta> tag in most cases.

Title meta descriptor renders <title> tag

A meta descriptor with the property { title } renders a <title> tag instead of a <meta> tag.

script:ld+json meta descriptor renders <script> tag

A meta descriptor with the property { "script:ld+json" } renders a <script type="application/ld+json"> tag. Its value should be a serializable object that is stringified and injected into the tag.

Example: structured data with script:ld+json

export function meta() { return [ { "script:ld+json": { "@context": "https://schema.org", "@type": "Organization", name: "React Router", url: "https://reactrouter.com", }, }, ]; } This example shows how to export structured data (JSON-LD) using the script:ld+json meta descriptor to provide schema.org Organization metadata.

Render link tag from meta descriptor with tagName property

A meta descriptor can render a <link> tag by setting the tagName property to "link". This is useful for SEO-related <link> tags like canonical URLs.

Use links export for asset links, not meta() for stylesheets and favicons

For asset links like stylesheets and favicons, use the links export instead of the meta() function with tagName="link". The meta() function with tagName="link" is specifically for SEO-related links like canonical URLs.

Example: canonical URL in meta descriptor

export function meta() { return [ { tagName: "link", rel: "canonical", href: "https://reactrouter.com", }, ]; } This example shows how to export a canonical link tag for SEO using the meta() function with tagName="link".

useBlocker hook to prevent navigation

The useBlocker hook from react-router prevents navigation based on a condition. It takes a callback that returns a boolean indicating whether to block navigation. Call blocker.proceed() to allow the blocked navigation to continue, or blocker.reset() to cancel the block and keep the user on the current page.

useBlocker state and UI confirmation flow

The blocker object has a state property that equals 'blocked' when navigation is blocked. Display confirmation UI conditionally when blocker.state === 'blocked', giving users buttons to call blocker.proceed() or blocker.reset().

useBlocker with useCallback for dirty state

When using useBlocker, pass a memoized callback via useCallback that returns the isDirty condition. The syntax is: let blocker = useBlocker(useCallback(() => isDirty, [isDirty])).

Reset blocker after form submission succeeds

Use a useEffect hook to monitor the fetcher.data for a successful response. When the action resolves successfully, reset the blocker with blocker.reset() so it no longer blocks navigation. This prevents the blocker from remaining active after the form has been submitted.

Proceed with blocked navigation after successful submission

After a form submission succeeds, you can choose to proceed with a blocked navigation instead of resetting the blocker. Call blocker.proceed() in the effect to allow the user to navigate to the originally blocked destination. This is useful when you want to redirect the user after form submission.

Navigation blocking with form example

Example: export default function Contact() { let [isDirty, setIsDirty] = useState(false); let fetcher = useFetcher(); let blocker = useBlocker(useCallback(() => isDirty, [isDirty])); let formRef = useRef<HTMLFormElement>(null); return ( <fetcher.Form ref={formRef} method="post" onChange={(event) => { let email = event.currentTarget.email.value; let message = event.currentTarget.message.value; setIsDirty(Boolean(email || message)); }} > <p> <label> Email: <input name="email" type="email" /> </label> </p> <p> <textarea name="message" /> </p> <p> <button type="submit"> {fetcher.state === "idle" ? "Send" : "Sending..."} </button> </p> {blocker.state === "blocked" && ( <div> <p>Wait! You didn't send the message yet:</p> <p> <button type="button" onClick={() => blocker.proceed()}> Leave </button>{" "} <button type="button" onClick={() => blocker.reset()}> Stay here </button> </p> </div> )} </fetcher.Form> ); }

Middleware warning - don't modify Response body

In server middleware, you should only read status/headers and set headers, not modify the Response body. In client middleware, the results value is read-only and represents the body/data for the resulting navigation which should be driven by loaders/actions, not middleware.

Middleware execution order - nested chain from parent to child

Middleware runs in a nested chain, executing from parent routes to child routes on the way down to route handlers, then from child routes back to parent routes on the way up after a Response is generated. For a GET /parent/child request, the execution order is: Root middleware start → Parent middleware start → Child middleware start → Run loaders, generate HTML Response → Child middleware end → Parent middleware end → Root middleware end.

Server middleware vs client middleware differences

Server middleware runs on the server in Framework mode for HTML Document requests and .data requests. It receives an HTTP Request and returns an HTTP Response via the next function. Client middleware runs in the browser for client-side navigations and fetcher calls. It does not have an HTTP Request or Response to bubble up. Instead, it bubbles up a Record<string, DataStrategyResult> keyed by route id, allowing post-processing based on loader/action outcomes.

When server middleware runs in Framework Mode

Server middleware only runs when hitting the server to prioritize SPA behavior and avoid creating unnecessary network activity. On document requests (GET /route), middleware runs whether loaders exist or not because the response encompasses both the loader and route component. On data requests (GET /route.data) for client-side navigations, server middleware only runs if a loader or action exists that requires a server request.

Force server middleware to run on every client-side navigation

To run certain server middlewares on every client-side navigation even if no loader exists, add a loader to the route that contains the middleware. This forces the middleware to always call the server for client-side navigations involving that route.

Client middleware always runs on every client navigation

Client middleware runs on every client navigation regardless of whether loaders exist, because it runs in the browser where a request to the router is always being made.

Middleware next() function behavior

The next() function runs the next middleware in the chain when called from a non-leaf middleware, or executes route handlers and generates the Response when called from leaf middleware. You can only call next() once per middleware; calling it multiple times throws an error. Code before await next() runs before handlers, code after runs after handlers.

Skipping next() in middleware

If you don't need to run code after handlers, you can skip calling next(). The next() function will be called automatically.

createContext for type-safe middleware context

Use createContext from react-router to create type-safe context objects that can be passed through the middleware chain. Example: import { createContext } from 'react-router'; export const userContext = createContext<User | null>(null);

RouterContextProvider in Framework mode getLoadContext

In Framework mode with a custom server, use a getLoadContext function to pass information to react router handlers. Create a RouterContextProvider instance and use context.set() to add values: function getLoadContext(req, res) { const context = new RouterContextProvider(); context.set(dbContext, createDb()); return context; }

getContext function in Data Mode

In Data Mode, pass a getContext function when creating the router to seed every navigation or fetcher call with shared values. This mirrors Framework mode's server-side getLoadContext. Example: const router = createBrowserRouter(routes, { getContext() { let context = new RouterContextProvider(); context.set(sessionContext, getSession()); return context; } });

Middleware export syntax in routes - Framework mode

Export middleware arrays from route files using the middleware property for server middleware and clientMiddleware property for client middleware. Server middleware export: export const middleware: Route.MiddlewareFunction[] = [authMiddleware]. Client middleware export: export const clientMiddleware: Route.ClientMiddlewareFunction[] = [timingMiddleware].

Middleware in route objects - Data Mode

In Data Mode, attach middleware arrays directly to route objects. Example: const routes = [{ path: '/', middleware: [timingMiddleware], Component: Root, children: [{ path: 'dashboard', middleware: [authMiddleware], loader: dashboardLoader, Component: Dashboard }] }];

Accessing context in loaders and actions

Both loaders and actions receive context as part of their arguments. In Framework mode: export async function loader({ context }: Route.LoaderArgs). In Data Mode: export async function dashboardLoader({ context }: LoaderFunctionArgs). Use context.get(contextKey) to retrieve values set by middleware.

AsyncLocalStorage alternative to context API

Node's AsyncLocalStorage API can be used alongside or instead of React Router's context API. Most modern runtimes support AsyncLocalStorage (Cloudflare, Bun, Deno). React Router provides a first-class context API for runtime-agnostic compatibility, but AsyncLocalStorage is especially powerful with React Server Components as it allows middleware information to be provided to Server Components and Server Actions in the same server execution context.

Context prevents naming conflicts and provides type safety

The context system provides type safety and prevents naming conflicts compared to adding properties directly. Type-safe approach: context.set(userContext, user) where userContext is typed as createContext<User>(). Old approach: context.user = user loses type safety and could be any value.

Authentication middleware pattern

Authentication middleware should check session for user, redirect to login if not found, and set user in context for access by loaders and actions. Example: export const authMiddleware = async ({ request, context }) => { const session = await getSession(request); const userId = session.get('userId'); if (!userId) { throw redirect('/login'); } const user = await getUserById(userId); context.set(userContext, user); };

Logging middleware pattern

Logging middleware can generate a request ID, log request details before calling next(), then log response status and duration after. Example: export const loggingMiddleware = async ({ request, context }, next) => { const requestId = crypto.randomUUID(); context.set(requestIdContext, requestId); console.log(`[${requestId}] ${request.method} ${request.url}`); const start = performance.now(); const response = await next(); const duration = performance.now() - start; console.log(`[${requestId}] Response ${response.status} (${duration}ms)`); return response; };

CMS redirect on 404 middleware pattern

Check response status after calling next(), and if 404, check CMS for a redirect. Example: export const cmsFallbackMiddleware = async ({ request }, next) => { const response = await next(); if (response.status === 404) { const cmsRedirect = await checkCMSRedirects(request.url); if (cmsRedirect) { throw redirect(cmsRedirect, 302); } } return response; };

Response headers middleware pattern

Middleware can add security headers to the response after calling next(). Example: export const headersMiddleware = async ({ context }, next) => { const response = await next(); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('X-Content-Type-Options', 'nosniff'); return response; };

Conditional middleware execution

Middleware can conditionally execute logic based on request properties. Example: export const middleware: Route.MiddlewareFunction[] = [async ({ request, context }, next) => { if (request.method === 'POST') { await ensureAuthenticated(request, context); } return next(); }];

Sharing context between action and loader in Framework mode

In Framework mode document POST requests, context set by middleware is shared between action and loader because they run in the same request. In SPA submissions, action and loader use separate POST/GET requests so context cannot be shared. This pattern always works in clientMiddleware/clientLoader/clientAction. Example: const sharedDataContext = createContext<any>(); export const middleware: Route.MiddlewareFunction[] = [async ({ request, context }, next) => { if (!context.get(sharedDataContext)) { context.set(sharedDataContext, await getExpensiveData()); } return next(); }];

Client-side timing middleware example

Client middleware can measure navigation timing. Example: async function timingMiddleware({ context }, next) { const start = performance.now(); await next(); const duration = performance.now() - start; console.log(`Navigation took ${duration}ms`); }

Client-side CMS fallback middleware with data results

Client middleware can inspect data strategy results from loaders/actions to take conditional action. Example checking for 404 from any route: async function cmsFallbackMiddleware({ request }, next) { const results = await next(); const found404 = Object.values(results).some((r) => isRouteErrorResponse(r.result) && r.result.status === 404); if (found404) { const cmsRedirect = await checkCMSRedirects(request.url); if (cmsRedirect) { throw redirect(cmsRedirect, 302); } } }

Middleware modes - Framework and Data

Middleware supports two modes: Framework mode (full server-side rendering) and Data mode (client-side routing with data loading). Framework mode supports both server middleware and client middleware. Data mode supports client middleware.

Resource routes return type options

Resource routes can return either Response instances or data() objects. Use Response instances when the resource route is intended for external consumption to keep response encoding explicit. Use data() when accessing resource routes from fetchers or Form submissions to maintain consistency with UI routes and enable streaming promises through Await.

Resource route PDF example

Example of a resource route that serves a PDF: ```tsx 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", }, }); } ``` This resource route has no default export, making it a resource route that serves a PDF file.

Resource route HTTP method handling example

Example showing how a resource route handles different HTTP methods: ```tsx 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", }); } ``` The loader handles GET requests and the action handles POST, PUT, PATCH, and DELETE requests.

Resource routes defined by convention

A route becomes a resource route when its module exports a loader or action but does not export a default component. Resource routes serve content like images, PDFs, JSON payloads, or webhooks instead of rendering React components.

Linking to resource routes with reloadDocument

When linking to resource routes, use <a> or <Link reloadDocument> to trigger a full page reload. Without reloadDocument, React Router will attempt to use client-side routing and fetching, which will fail.

Resource routes HTTP method handling

GET requests to resource routes are handled by the loader function. POST, PUT, PATCH, and DELETE requests are handled by the action function.

RSC Data Mode route configuration with matchRSCServerRequest

In RSC Data Mode, routes are configured as an argument to matchRSCServerRequest. At minimum, each route needs a path and component. Using the lazy() option with Route Modules is recommended for startup performance and code organization. The lazy field expects the same exports as the Route Module API.

Route module lazy loading pattern in RSC Data Mode

In RSC Data Mode, routes should use lazy() with dynamic imports to load route modules for startup performance and code organization. The lazy field expects exports like loader, action, meta, links, headers, ErrorBoundary, HydrateFallback, and client annotations (clientLoader, clientAction, shouldRevalidate).

ServerComponent export for server-rendered routes

In RSC Framework Mode, if a route exports a ServerComponent instead of the typical default component export, the route renders on the server instead of the client. A route module cannot export both default and ServerComponent. Other route module component exports have server counterparts: ServerErrorBoundary (vs ErrorBoundary), ServerLayout (vs Layout), and ServerHydrateFallback (vs HydrateFallback).

Server component route module exports

In RSC Framework Mode, the following exports have mutually exclusive server and client counterparts: ServerComponent/default, ServerErrorBoundary/ErrorBoundary, ServerLayout/Layout, ServerHydrateFallback/HydrateFallback. Client-only annotations like clientLoader and clientAction can be exported alongside a ServerComponent.

MDX route support in RSC Framework Mode

MDX routes are supported in RSC Framework Mode when using @mdx-js/rollup v3.1.1 or later. Components exported from an MDX route must be valid in RSC environments and cannot use client-only features like hooks. Extract components needing client features into a client module with "use client" directive.

Client properties in RSC Data Mode routes

In RSC Data Mode, routes defined on the server can still provide clientLoader, clientAction, and shouldRevalidate through client references and "use client". These can be re-exported from lazy loaded route modules. This is also how to make an entire route a Client Component.

Type-only auto-imports with verbatimModuleSyntax

When auto-importing the Route type helper, TypeScript normally generates: import { Route } from "./+types/my-route". If verbatimModuleSyntax is enabled in tsconfig.json under compilerOptions, TypeScript will automatically add the type modifier: import type { Route } from "./+types/my-route". This helps tools like bundlers detect type-only modules that can be safely excluded from the bundle.

Route module type safety setup overview

React Router generates route-specific types to power type inference for URL params, loader data, and more. Type safety in React Router can be set up by adding .react-router/ to .gitignore, including generated types in tsconfig, generating types before type checking, and optionally enabling type-only auto-imports.

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 added to .gitignore with the entry '.react-router/'.

Configure tsconfig for generated types

Edit tsconfig.json to include generated types and configure rootDirs. The include array should contain '.react-router/types/**/*'. The compilerOptions should set rootDirs to ['.', './.react-router/types']. This allows types to be imported as relative siblings to route modules.

tsconfig example for type safety

Example tsconfig.json configuration for route module type safety: { "include": [".react-router/types/**/*"], "compilerOptions": { "rootDirs": [".", "./.react-router/types"] } }

Multiple tsconfig files with route modules

If using multiple tsconfig files for your app, changes for .react-router/types must be made in whichever tsconfig includes your app directory, not necessarily in the root tsconfig.json. For example, if tsconfig.vite.json includes the app directory, that is the one that should configure .react-router/types for route module type safety.

Generate types before type checking in CI

To run type checking as a separate command (such as in a CI pipeline), generate types before running typechecking. Example package.json script: { "scripts": { "typecheck": "react-router typegen && tsc" } }

Automatic type generation in development

React Router's Vite plugin automatically generates types into .react-router/types/ whenever you edit your route config (routes.ts). Running react-router dev (or your custom dev server) will generate up-to-date types in your routes.

SPA Mode pre-renders root route at build time

In SPA Mode, React Router pre-renders your root route at build time into an `index.html` file. This allows you to send more than an empty `<div>`, use a root `loader` to load data for the application shell, and use React components to generate the initial page users see via `HydrateFallback`.

Give your agent this brain