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 · API · all subjects

framework conventions

59 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Route type import path convention

Route-specific types are imported from ./+types/{route-name} where {route-name} matches the route module filename. For example, a route module at app/routes/my-route.tsx imports from ./+types/my-route.

React Router generates route-specific types

React Router generates route-specific types to power type inference for URL params, loader data, and more.

Type-only imports with verbatimModuleSyntax

When enabling verbatimModuleSyntax in tsconfig.json compilerOptions, TypeScript will automatically generate the type modifier for Route type imports. Instead of import { Route } from "./+types/my-route", it generates import type { Route } from "./+types/my-route". This helps bundlers detect type-only modules that can be safely excluded from the bundle.

Typegen planning focuses on path params, loader data, and action data

The initial implementation of typegen targets type inference for path parameters, loader data, and action data. The foundation enables future expansion to include type inference for Link targets and search parameters.

Same typegen code path for programmatic and file-based routing

React Router treats any value returned by routes.ts identically regardless of how routes were constructed. React Router will run typegen in all cases rather than maintaining separate code paths for programmatic routing versus file-based routing.

Type inference goals for React Router v7

React Router aims to achieve type inference in three areas: from the route config, within a route module, and across route modules. The goal is to infer path parameters, loader data, action data, and other types automatically without requiring users to manually specify generics to useParams, useLoaderData, and useActionData.

Path params and loader data as props instead of hooks

The planned approach is to pass route-specific data as props to route exports rather than through hooks. Each route export receives route-specific arguments: loader receives LoaderArgs with params, clientLoader receives ClientLoaderArgs with params and serverLoader, and the default component receives DefaultProps with params, loaderData, and actionData. Hooks like useParams, useLoaderData, and useActionData will be kept for backwards compatibility but eventually deprecated.

Typegen generates types for each route module

React Router generates types for each route module into a gitignored .react-router/types directory that mirrors the route module structure. For example, app/routes/product.tsx has generated types in .react-router/types/app/routes/+types.product.ts. The tsconfig.json rootDirs option allows importing typegen files as siblings: import { LoaderArgs, DefaultProps } from "./+types.product" within app/routes/product.tsx.

Watch mode for typegen keeps types in sync

React Router provides a react-router typegen --watch command that automatically regenerates types as files change during development. This prevents typegen files from becoming out of sync, which is a common problem with code generation solutions.

Why defineLoader and similar helpers were rejected

Helper functions like defineLoader were rejected because they add significant code noise, introduce a runtime API that exists only for type safety, and their implementation using extends generics does not pinpoint incorrect return statements precisely, making TypeScript errors harder to debug.

Why defineRoute was rejected

A single defineRoute export was considered but rejected because: type inference across function arguments depends on ordering (placing Component before loader breaks inference), it cannot infer types from the route config in routes.ts (no type safety for path params or Link targets), and it breaks tree-shaking and React Fast Refresh since bundlers and HMR tools expect module exports, not function calls.

Why zero-effort type safety via language service plugin was rejected

A TypeScript language service plugin approach like Svelte Kit's was rejected because: tools like typescript-eslint that statically inspect types would not see injected types, running tsc directly would bypass the plugin, and React Router prefers invoking tsc directly in package.json scripts since it is pure TypeScript (unlike Svelte which needs svelte-check).

Why TypeScript plugin for typegen was rejected

A TypeScript plugin that runs typegen in watch mode was initially created but rejected because: it silently fails if dependencies are not installed before opening the project, it requires the workspace version of TypeScript which VSCode does not use by default, and debugging requires running the Open TS Server log command and sifting through verbose logs, making setup unclear and troubleshooting tedious.

Use <Form> and useNavigation when URL should change

When navigating or transitioning between pages, or after actions like creating or deleting records where the URL should change, use <Form> with useNavigation. Expected behavior includes browser history accurately reflecting the user's journey. Users should be able to use the back button to return to the previous page, or the history entry may be replaced but the URL change is important.

Deleting a record scenario and context preservation

When a user is on a page dedicated to a specific record and deletes it, redirect them to a general page such as a list of all records. This requires a URL change. Conversely, when deleting a record from a list view, the user should remain on the list, making useFetcher appropriate for maintaining context.

Creating a record scenario requires URL change

After creating a new record, redirect users to a page dedicated to that new record where they can view or further modify it. This requires a URL change and is a typical use case for <Form> with useNavigation.

Form vs fetcher: URL change decision criteria

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

Use useFetcher when URL should not change

Use useFetcher for actions that don't significantly change the context or primary content of the current view, such as updating individual fields, minor data manipulations, deleting records from a list, creating records in a list view, or loading data for popovers and comboboxes. These actions do not warrant a new URL or page reload.

Generated route types structure

React Router generates types for each route in a +types/<route file>.d.ts file within the .react-router/types/ directory. The following types are generated for each route: LoaderArgs, ClientLoaderArgs, ActionArgs, ClientActionArgs, HydrateFallbackProps, ComponentProps (for the default export), and ErrorBoundaryProps.

Importing route-specific types

Route-specific types can be imported from a generated file using: import type { Route } from "./+types/<routename>". This provides type safety for loader arguments, component props, and other route exports. TypeScript can import these generated files as if they were right next to their corresponding route modules through rootDirs configuration.

Catch-all route returns 200 by default

A $.tsx file acts as a catch-all route matching any URL that does not match other routes. By default it returns a 200 response status. To return a 404 for unmatched routes, the loader should return data({}, 404).

Index route file naming

_index.tsx is the special filename that creates an index route for its parent route. When placed in app/routes/ it matches the root URL /.

File extensions for routes

Route files can use .js, .jsx, .ts, or .tsx file extensions.

Dot delimiter creates nested URLs

Adding a dot (.) to a route filename creates a forward slash (/) in the URL path. For example, concerts.trending.tsx matches /concerts/trending. Dot delimiters also create layout nesting.

Dynamic segments with $ prefix

Dynamic URL segments are created by prefixing a filename segment with $. For example, concerts.$city.tsx creates a dynamic parameter accessible as params.city in loaders and actions. The parameter name derives from the filename segment after the $.

Multiple dynamic segments in routes

Routes can have multiple dynamic segments, like concerts.$city.$date, and each parameter is accessible on the params object by its name: params.date and params.city.

Nested routes with dot delimiters

When a filename before the first dot matches another route filename, it automatically becomes a child route. For example, concerts.tsx becomes the parent of concerts._index.tsx, concerts.$city.tsx, and concerts.trending.tsx. Child routes render inside the parent route's outlet.

Trailing underscore opts out of layout nesting

A trailing underscore in a filename segment (e.g., concerts_.mine.tsx) creates a URL path segment but prevents layout nesting. concerts_.mine.tsx matches /concerts/mine but does not nest under the concerts.tsx layout. Instead it nests under the root layout.

Leading underscore creates pathless routes

A leading underscore on a route segment (e.g., _auth.login.tsx) creates a route that shares a layout without adding path segments to the URL. _auth.login.tsx and _auth.register.tsx both match URLs /login and /register and share the _auth.tsx layout, but _auth does not appear in the URL.

Optional segments with parentheses

Wrapping a route segment in parentheses makes that segment optional. For example, ($lang)._index.tsx matches both / and /en/ and /fr/. Optional segments match eagerly.

Splat routes with $ match remaining path

A splat route using $ as the entire segment (e.g., $.tsx or files.$.tsx) matches the rest of a URL including all remaining slashes. The matched path value is accessed via params['*']. For example, /files/talks/react-conf.pdf matches files.$.tsx with params['*'] = 'talks/react-conf.pdf'.

Escaping special characters in filenames

Special route convention characters (dot, underscore, $, parentheses, brackets) can be escaped using square brackets. For example: sitemap[.]xml.tsx matches /sitemap.xml, dolla-bills-[$].tsx matches /dolla-bills-$, reports.$id[.pdf].ts matches /reports/123.pdf.

Route folders with route.tsx

Routes can be organized as folders with a route.tsx file inside. Other files in the folder do not become routes and can hold components and utilities used by that route. The folder name defines the route path completely. For example, app/routes/app._index/route.tsx is equivalent to app/routes/app._index.tsx.

Route modules foundation

Route modules are the foundation of React Router's data features. They define data loading, actions, revalidation, error boundaries, and other route-level configuration.

Dev script for React Router Vite plugin

In package.json scripts, use 'react-router dev' as the dev script to run the app with the React Router Vite plugin.

Vite plugin features: loaders, actions, code-splitting, scroll restoration, pre-rendering, SSR

The React Router Vite plugin adds route loaders, actions, automatic data revalidation, type-safe route modules, automatic route code-splitting, automatic scroll restoration across navigations, optional static pre-rendering, and optional server rendering.

.react-router directory should be added to .gitignore

The .react-router/ directory is generated during development and should be added to .gitignore to avoid tracking unnecessary files in the repository.

middleware example: logging requests on server

Example middleware that logs request method, URL, and response status with duration. It runs on the server and uses next() to continue the chain, then returns the response.

clientMiddleware export in route modules

clientMiddleware is the client-side equivalent of middleware and runs in the browser during client navigations. Unlike server middleware, client middleware does not return Responses because it is not wrapping an HTTP request on the server.

headers export function

The route headers function defines the HTTP headers to be sent with the response when server rendering. It returns an object with header names as keys and header values as values.

handle export in route modules

Route handle allows apps to add anything to a route match in useMatches to create abstractions like breadcrumbs. The handle object can contain any custom data.

middleware export in route modules

Route middleware runs sequentially on the server before and after document and data requests. The next function continues down the chain, and on the leaf route executes loaders/actions for the navigation. Middleware provides a singular place for logging, authentication, and post-processing of responses.

headers export example

Example showing headers function that returns object with custom header 'X-Stretchy-Pants' and cache control header 'Cache-Control: max-age=300, s-maxage=3600'.

clientMiddleware example: logging on client

Example client middleware that logs request method and URL on initial client navigation and response duration. Unlike server middleware, it does not return a Response.

middleware example: authentication check

Example middleware that checks for logged-in users by retrieving session data, throwing a redirect to /login if userId is not found, and setting the user in context for access by loaders.

Loaders are called before route component renders

As the user navigates between routes, the loaders are called before the route component is rendered.

Unstable flags shipped in SemVer patch releases

Unstable flags are shipped in SemVer patch releases because they are not new stable or documented APIs. When an unstable flag stabilizes into a Future Flag, that will be released in a SemVer minor release and will be properly documented and added to the Future Changes Guide.

Unstable flags tracked in CHANGELOG

To learn about current unstable flags, keep an eye on the CHANGELOG.

Future Flags for breaking API changes

When an API changes in a breaking way, it is introduced in a future flag. This allows you to opt-in to one change at a time before it becomes the default in the next major version. Without enabling the future flag, nothing changes about your app. Enabling the flag changes the behavior for that feature.

Unstable Flags not recommended for production

Unstable flags are for features still being designed and developed. They are not recommended for production because they will change without warning and without upgrade paths, they will have bugs, they aren't documented, and they may be scrapped completely. When you opt-in to an unstable flag you are becoming a contributor to the project rather than a user.

Future Flags documented in Future Changes Guide

All current future flags are documented in the Future Changes Guide to help you stay up-to-date with planned API changes.

State management approach in React Router

React Router seamlessly bridges the gap between 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.

Proper places to store state in React Router

Rather than React state, store data in: URL search params for filtering and view preferences, cookies for persistent user preferences that need server access, server sessions for user authentication state, and server caches for frequently accessed data.

Why traditional caching solutions are redundant in React Router

Popular caching solutions like Redux, TanStack Query, and Apollo become redundant in React Router because React Router inherently handles data synchronization. Most React Router applications forgo these libraries entirely by leveraging React Router's built-in mechanisms like loaders, actions, revalidation, and hooks like useNavigation and useFetcher.

Framework-agnostic router core

The @remix-run/router package contains a framework-agnostic router with zero dependencies. The bulk of routing logic (determining current/next routes, loading data, interrupting navigations) is separated from the React-specific rendering layer, enabling future support for other UI libraries like Preact and Vue.

Remix migration to React Router 6.4 - Four functional aspects

The migration of Remix on top of React Router 6.4 involves four separate functional aspects: (1) Server data loading, (2) Server react component rendering, (3) Client hydration, and (4) Client data loading. Aspect (1) can be implemented and deployed in isolation. Aspects (2) and (3) must happen together since the contexts and components need to match. Aspect (4) comes automatically since loaders and actions are included on the routes created in (3).

SSR implementation approach - three phases

The UI rendering layer migration in @remix-run/react can be done iteratively in two phases: First, focus on rendering the SSR document properly without Scripts. Second, add client-side hydration. However, both SSR and client HTML must stay synced, and associated hooks must read from the same contexts, so they cannot be deployed iteratively.

Config changes do not trigger full dev server reloads

Changes to the `react-router.config.ts` file no longer trigger full dev server reloads, allowing for more graceful handling of config updates.

app/routes.ts changed to default export

The `app/routes.ts` API was changed to use a default export instead of a named `routes` export to maintain internal consistency with the new `react-router.config.ts` configuration pattern.

Give your agent this brain