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

architecture

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

Backend for Frontend architecture pattern

The Backend for Frontend (BFF) strategy employs a web server with a job scoped to serving the frontend web app and connecting it to the services it needs: database, mailer, job queues, existing backend APIs (REST, GraphQL), etc. Instead of the UI integrating directly from the browser to these services, it connects to the BFF, and the BFF connects to your services.

Advantages of BFF: keep secrets out of client bundles

By using a Backend for Frontend architecture with React Router, you can keep third-party API tokens and secrets on the server using environment variables like process.env.API_TOKEN, preventing them from being exposed in client-side bundles.

React Router as backend for frontend

React Router can serve as a backend for frontend in mature apps that already have backend application code in Ruby, Elixir, PHP, or other languages. This eliminates the need to migrate existing backend code to a server-side JavaScript runtime just to benefit from React Router. The React Router app can connect the frontend UI to existing backend services.

Advantages of BFF: move code to server to reduce bundle size

By moving code like HTML escaping and other utility functions from browser bundles to the server, you can speed up your app and make code easier to maintain. Server-side code does not need to worry about UI states for async operations, simplifying the logic.

Advantages of BFF: prune data to reduce network transfer

A Backend for Frontend architecture allows you to prune response data from your backend services before sending it to the browser. This reduces the amount of data sent over the network, speeding up the application significantly.

API comparison: Form vs fetcher equivalents

Navigation/URL APIs and Fetcher APIs have parallel functionality: <Form> corresponds to <fetcher.Form>, actionData (component prop) corresponds to fetcher.data, navigation.state corresponds to fetcher.state, navigation.formAction corresponds to fetcher.formAction, and navigation.formData corresponds to fetcher.formData.

When to use useFetcher: URL-preserving actions

Use useFetcher for actions that should not change the URL, including updating a single field in a list, deleting a record from a list view, creating a record in a list view, and loading data for popovers or comboboxes. useFetcher maintains user context by keeping the URL unchanged while managing state transitions independently.

Form vs fetcher: URL change is the primary decision criterion

The primary criterion when choosing between <Form>, useFetcher, and useNavigation is whether you want the URL to change or not. Use <Form> and useNavigation when the URL should change (navigating between pages, creating or deleting records). Use useFetcher when the URL should remain unchanged (updating individual fields, deleting items from a list, loading data for popovers or comboboxes).

When to use Form: URL-changing actions

Use <Form> combined with useNavigation when performing actions that should change the URL. This applies to creating new records (redirect to the new record's page) and deleting a record from a dedicated page (redirect to a list or other relevant page). These actions reflect significant changes to the user's context and should update the browser history.

Link component discover attribute

Link and NavLink components support a discover attribute to control discovery behavior. The discover attribute can be set to "none" to opt out of discovery for specific links. By default, links are automatically discovered.

Lazy Route Discovery definition and purpose

Lazy Route Discovery is a performance optimization that loads route information progressively as users navigate through an application, rather than loading the complete route manifest upfront. With Lazy Route Discovery enabled (the default), React Router sends only the routes needed for the initial server-side render in the manifest. As users navigate to new parts of the application, additional route information is fetched dynamically and added to the client-side manifest.

Lazy route discovery configuration example

Example configurations for react-router.config.ts: default lazy discovery uses mode: "lazy" with manifestPath: "/__manifest"; custom manifest path example uses routeDiscovery: { mode: "lazy", manifestPath: "/my-app-manifest" }; disable lazy discovery example uses routeDiscovery: { mode: "initial" }.

Lazy Route Discovery performance benefits

Lazy Route Discovery provides three main performance improvements: (1) Faster Initial Load - smaller initial bundle size by excluding unused route metadata; (2) Reduced Memory Usage - route information is loaded only when needed; (3) Scalability - applications with hundreds of routes see more significant benefits.

Eager Discovery Optimization for Link components

To prevent navigation waterfalls, React Router implements eager route discovery. All Link and NavLink components rendered on the current page are automatically discovered via a batched request to the server. This discovery request typically completes before users click any links, making subsequent navigation feel synchronous even with lazy route discovery enabled.

Route Discovery Process steps

When a user navigates to a new route not in the current manifest, the process follows four steps: (1) Route Discovery Request - React Router makes a request to the internal /__manifest endpoint; (2) Manifest Patch - the server responds with the required route information; (3) Route Loading - React Router loads the necessary route modules and data; (4) Navigation - the user navigates to the new route.

react-router.config.ts routeDiscovery configuration

Route discovery behavior is configured in react-router.config.ts using the routeDiscovery option. The configuration object has two properties: mode (string, required) - set to "lazy" for lazy discovery or "initial" to disable lazy discovery and include all routes initially; manifestPath (string, optional) - the endpoint path for manifest requests, defaults to "/__manifest" and can be customized for multiple applications on the same domain.

Deployment considerations for lazy route discovery

When deploying applications with lazy route discovery, ensure: (1) Route Handling - /__manifest requests (or custom manifestPath) reach the React Router handler; (2) CDN Caching - if using CDN/edge caching, include version and paths query parameters in the cache key for the manifest endpoint; (3) Multiple Applications - use a custom manifestPath if running multiple React Router applications on the same domain.

Route manifest content and structure

The route manifest contains metadata about routes including JavaScript/CSS imports and whether routes have loaders/actions, but not the actual route module implementations. This allows React Router to understand the application's structure without downloading unnecessary route information.

Link component progressive enhancement

A `<Link to="/account">` renders an `<a href="/account">` tag that works without JavaScript. When JavaScript loads, React Router intercepts clicks and handles navigation with client-side routing, providing better UX control while maintaining functionality without JavaScript.

How server rendering improves performance vs SPAs

Server rendering allows React Router apps to do more work in parallel than a typical Single Page App. SPAs send a blank document and only start work when JavaScript loads, causing a waterfall effect. React Router apps start work the moment the request hits the server and can stream the response so the browser downloads JavaScript, assets, and data in parallel, resulting in faster initial load and subsequent navigation.

Progressive enhancement in React Router with SSR

When using React Router with Server-Side Rendering (the default in framework mode), you can automatically leverage the benefits of progressive enhancement.

useFetcher for progressive enhancement without navigation

The `useFetcher` hook can enhance a form without causing navigation like `<Form>` does. When JavaScript loads, `useFetcher` allows the user to stay on the same page while submitting data. Before JavaScript loads, the form still works as a standard submission.

URL as source of truth reduces client-side state

When you build with URLs as the foundation, you don't need to manage client-side state for UI state. The URL serves as the source of truth, reducing complexity and making the app easier to reason about and maintain.

Three core principles of progressive enhancement in React Router

React Router embraces progressive enhancement for three main reasons: (1) Performance—while users think only 5% have slow connections, the reality is 100% of users have slow connections 5% of the time; (2) Resilience—everybody has JavaScript disabled until it's loaded; (3) Simplicity—building apps in a progressively enhanced way with React Router is actually simpler than building a traditional SPA.

Progressive enhancement definition and strategy

Progressive enhancement is a web design strategy that prioritizes web content first, allowing all users to access basic content and functionality, while users with additional browser features or faster internet access receive an enhanced version. It was coined in 2003 by Steven Champeon & Nick Finck during an era of inconsistent CSS and JavaScript support across browsers.

useFetcher progressive enhancement example

The following component starts as a basic form submission but adds pending UI when JavaScript loads: ```tsx 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 shows how to progressively enhance a form to show pending state without changing the fundamental feature design.

Form-based progressive enhancement without JavaScript

A form that uses `<Form method="post" action="/add-to-cart">` with a submit button works regardless of whether JavaScript has loaded. The server handles the form submission. When JavaScript loads, React Router intercepts the form submission and handles it client-side, allowing you to add pending UI or other client-side behavior without changing the fundamental code.

SearchBox progressive enhancement with URL and useNavigation

A search form can start as a simple form submission without state management: ```tsx export function SearchBox() { return ( <Form method="get" action="/search"> <input type="search" name="query" /> <SearchIcon /> </Form> ); } ``` Then be progressively enhanced by checking navigation state to show a spinner: ```tsx 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> ); } ``` This demonstrates how progressive enhancement works without fundamental architectural changes.

Progressive enhancement example: Add to Cart form

The following component works without JavaScript and can be enhanced with client-side behavior: ```tsx 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> ); } ``` This demonstrates how the same form works as a full-page submission when JavaScript isn't available, and can be progressively enhanced later.

Progressive enhancement development approach

Progressive enhancement is an iterative development approach: start with the simplest version of a feature using basic web fundamentals (HTML and URLs) and ship it, then iterate to add enhanced user experience when JavaScript loads. This means building it as iterations of the same feature, not building it two completely different ways (one for JavaScript and one without).

React Router special files overview

React Router has several special configuration and entry files that control how an application is set up. The main special files are: react-router.config.ts (optional configuration), root.tsx (required root route), routes.ts (required route configuration), entry.client.tsx (optional client-side entry), entry.server.tsx (optional server-side entry), .server modules (server-only), and .client modules (client-only).

root.tsx file requirement

root.tsx is a required file that serves as the root route and renders the HTML document.

entry.server.tsx file purpose

entry.server.tsx is an optional server-side entry point used for rendering in React Router applications.

react-router.config.ts file purpose

react-router.config.ts is an optional configuration file for your React Router application.

OpenTelemetry integration pattern

You can integrate OpenTelemetry by creating spans around instrumented calls. The pattern wraps handler calls with tracer.startActiveSpan() using labels and attributes derived from the instrumentation info. After awaiting the handler, check if error exists on the result and record the exception to the span with span.recordException(error). Set span status to SpanStatusCode.ERROR if an error occurred. Use route id and pattern as span attributes. This works for handler request instrumentation and route loader/action/middleware instrumentations.

Performance impact of instrumentation

Adding instrumentation code execution at runtime may alter the performance characteristics compared to an uninstrumented application. Keep this in mind and perform appropriate testing and/or leverage conditional instrumentation to avoid a negative user experience impact in production.

Client-side performance tracking pattern with Performance API

You can track client-side performance using the Performance API by creating marks and measures around instrumented calls. For navigations and fetches, create marks with performance.mark(`start:${label}`) before calling the handler and performance.mark(`end:${label}`) after. Then measure with performance.measure(label, `start:${label}`, `end:${label}`). For routes, wrap loader, action, and middleware similarly with marks and measures. This integrates with browser DevTools performance profiling.

Conditional instrumentation based on environment

You can enable instrumentation conditionally based on environment or other factors. You can conditionally return different instrumentation arrays based on process.env.NODE_ENV or other conditions. Within an individual instrumentation, you can also conditionally call instrument() based on route.id, query parameters, or other criteria by returning early without calling the instrument() method.

Composing multiple instrumentations

You can provide an array of multiple instrumentations to compose them. Each instrumentation wraps the previous one, creating a nested execution chain. For example: export const instrumentations = [loggingInstrumentation, performanceInstrumentation, errorReportingInstrumentation];

Result metadata available from instrumented calls

Client navigation/fetcher and server request handler instrumentations return a meta field in the result object. The meta field contains url (normalized URL for the matched route request), pattern (matched route pattern like /projects/:id), and params (matched route params). Meta may be undefined when React Router does not have route metadata for the instrumented call, such as server manifest requests or numeric POP navigations like navigate(-1). For client navigations that redirect, meta describes the original navigation target instead of the final redirected location. Server request handler instrumentations also return statusCode. Route-level instrumentations do not include meta in the result because metadata is available on the info parameter.

Route-level instrumentation info parameter

Route-level instrumentations (loader, action, middleware, lazy) receive an info parameter as the second argument containing: params (route parameters), request (the Request object), context (server context in Framework Mode), and pattern (the matched route pattern like /projects/:id). The lazy() instrumentation only receives the callLazy function without an info parameter.

Router-level instrumentation (client)

Router-level instrumentation instruments client-side router operations like navigations and fetcher calls. The router.instrument() method accepts async navigate() and async fetch() functions. The navigate() function receives callNavigate and info with to and currentUrl properties. The fetch() function receives callFetch and info with href, currentUrl, and fetcherKey properties. Both handlers can access result.meta?.pattern after awaiting their respective call functions.

Handler-level instrumentation (server)

Handler-level instrumentation instruments the top-level request handler that processes all requests to your server. It runs around ALL requests to your app. The handler.instrument() method accepts an async request() function that receives handleRequest as the first argument and an info object with request and context properties. After awaiting handleRequest(), you can access result.statusCode and result.meta?.pattern from the returned result object.

Client-side instrumentation in Data Mode

In Data Mode, you add instrumentations when creating your router with createBrowserRouter. Pass an instrumentations option object to createBrowserRouter alongside your routes. The instrumentations object has the same structure as Framework Mode: a router() method for router operations (navigate and fetch) and a route() method for individual route handlers (loader, action, middleware, lazy). The router object in the router() method provides the instrument() method with navigate() and fetch() functions. This is then passed to RouterProvider as the router prop.

Client-side instrumentation in Framework Mode entry.client.tsx

In Framework Mode, client-side instrumentations are passed to the HydratedRouter component via the instrumentations prop in entry.client.tsx. The instrumentations object has a router() method to instrument router operations and a route() method for individual routes. The router.instrument() method accepts async navigate() and fetch() functions. The navigate() function receives callNavigate and an info object with currentUrl and to. The fetch() function receives callFetch and an info object with href, currentUrl, and fetcherKey. Route-level instrumentations work the same as server-side, instrumenting loader, action, middleware, and lazy. The router can return result.meta?.pattern after calling the handlers.

Request logging pattern with handler and route instrumentation

A common pattern for request logging instruments both the handler and routes. The handler's request instrumentation logs the URL with timing information, status code, and route pattern. The route instrumentation logs middleware, loader, and action execution with timing. The log function wraps handlers to log start and elapsed time. Example: console.log(`-> request ${request.url}`); let start = Date.now(); let result = await fn(); console.log(`<- request ... (${Date.now() - start}ms ${result.statusCode} ${pattern})`);

Server-side instrumentation in Framework Mode entry.server.tsx

In Framework Mode, you export an instrumentations array in entry.server.tsx. The instrumentations object has a handler() method to instrument the server handler and a route() method to instrument individual routes. The handler.instrument() method accepts an async request() function that receives handleRequest and an info object with the request and context. The route.instrument() method accepts async loader(), action(), middleware(), and lazy() functions. Each route instrumentation receives a callHandler function and an info object with params, request, context, and pattern. Example: the handler's request function logs before and after handleRequest(), accessing result.statusCode and result.meta?.pattern.

Instrumentation overview and purpose

Instrumentation allows you to add logging, error reporting, and performance tracing to your React Router application without modifying your actual route handlers. With the React Router Instrumentation APIs, you provide wrapper functions that execute around your request handlers, router operations, route middlewares, and route handlers. This enables monitoring application performance, adding logging, integrating with observability platforms (Sentry, DataDog, New Relic, etc.), implementing OpenTelemetry tracing, and tracking user behavior and navigation patterns. Instrumentation is read-only — you can observe what's happening but cannot modify runtime application behavior by modifying the arguments passed to, or data returned from your route handlers.

Pre-rendering with SPA fallback for hybrid approach

When using ssr:false, you can limit the prerender config to specific paths and React Router will also output a SPA Fallback HTML file that can hydrate any other paths. The fallback is written to build/client/index.html if the / path is not pre-rendered, or build/client/__spa-fallback.html if the / path is pre-rendered.

Pre-rendered output files in build/client directory

Pre-rendered results are written to the build/client directory with two files for each path: [url].html for initial document requests and [url].data for client side navigation requests. During development, pre-rendering doesn't save rendered results; this only happens during react-router build.

Pre-rendering works with ssr:true or ssr:false

Pre-rendering can be used in two ways based on the ssr config value: alongside a runtime SSR server with ssr:true (the default), or deployed to a static file server with ssr:false.

Pre-render paths using async function

If you need to perform complex or asynchronous logic to determine the paths, you can provide a function that returns an array of paths. The function receives a getStaticPaths method that returns all static paths in your application, allowing you to avoid manually adding them.

Pre-render specific paths including dynamic values

To configure specific paths including dynamic values, you can specify an array of paths in the prerender config. For example: prerender: ['/', '/blog', '/blog/post1', '/blog/post2'] to pre-render both static and specific dynamic paths.

prerender: true pre-renders all static paths

Setting prerender to a boolean true will pre-render all of the application's static paths based on routes.ts. Dynamic paths like /blog/:slug are not included because parameter values are unknown.

Enable pre-rendering with prerender config

Pre-rendering is enabled via the prerender config in react-router.config.ts file.

Pre-rendering speeds up static content delivery

Pre-rendering allows you to speed up page loads for static content by rendering pages at build time instead of at runtime.

Invalid exports when ssr:false

When pre-rendering with ssr:false, React Router will error at build time for invalid exports: headers/action functions are prohibited in all routes because there is no runtime server; when using ssr:false without prerender (SPA Mode), loaders are only permitted on the root route; when using ssr:false with prerender, loaders are permitted on any route matched by a prerender path. If using a loader on a pre-rendered route with child routes, either pre-render all child routes or use a clientLoader on the parent for non-pre-rendered child paths.

ssr:false without prerender is SPA Mode

If you specify ssr:false without a prerender config, React Router uses SPA Mode, which renders a single HTML file that can hydrate for any application path. It only renders the root route into the HTML file and determines which child routes to load based on the browser URL during hydration. Loaders can only be on the root route in SPA Mode.

Configure server to serve SPA fallback file

You can configure your deployment server to serve the SPA fallback file for any path that would otherwise 404. Some hosts support a _redirects file: use /* /index.html 200 if you did not pre-render /, or /* /__spa-fallback.html 200 if you pre-rendered /. The sirv-cli tool can also serve this with --single index.html or --single __spa-fallback.html.

What presets can do

Presets can only do two things: configure React Router config options on your behalf, and validate the resolved config.

Give your agent this brain