React Router

repository·main·Indexed 13 days ago

https://github.com/remix-run/react-router

A versatile routing solution for React that functions as both a lightweight client-side library and a full-featured web framework. It includes specialized packages for various runtimes including Node.js, Express, and Cloudflare, and provides tools for server-side rendering (SSR), code splitting, and Hot Module Replacement (HMR) via the @react-router/dev package.

Tokens
217.2K
Snippets
673
Records
860
Agent score
95%

What's inside React Router

  1. Overview of React Router usage strategies

    main

    React Router is a multi-strategy router for React designed to be flexible in how it is integrated into your application. You can use it in two primary ways:

    1. As a Framework: Use React Router as a full-stack framework with built-in features like server-side rendering, data loading, and optimized routing. This is the maximal usage pattern.
    2. As a Library: Use React Router minimally as a client-side routing library within your own existing architecture. This is the minimal usage pattern.

    Depending on your choice, you will follow different installation and configuration paths.

  2. What is a Route Module?

    main

    A Route Module is a file referenced in your routes.ts configuration that serves as the foundation for React Router's framework features. It is responsible for defining automatic code-splitting, data loading, actions, revalidation, error boundaries, and more.

    Example route definition in app/routes.ts:

    route("teams/:teamId", "./team.tsx"),
    //           ^^^^^^^^^^^^^^^^^^^^^^^^
    //           This file is the Route Module
    route("teams/:teamId", "./team.tsx")
  3. What is React Router Instrumentation?

    main

    Instrumentation allows you to add logging, error reporting, and performance tracing to your React Router application without modifying your actual route handlers. It provides "wrapper" functions that execute around request handlers, router operations, route middlewares, and route handlers.

    Key Use Cases:

    • Monitoring application performance
    • Adding logging
    • Integrating with observability platforms (e.g., Sentry, DataDog, New Relic)
    • Implementing OpenTelemetry tracing
    • Tracking user behavior and navigation patterns

    Core Design Principle: Instrumentation is read-only. You can observe runtime activity, but you cannot modify the application's behavior by changing the arguments passed to or the data returned from your route handlers.

  4. Understand development mode behavior in @react-router/serve

    main

    In development mode (NODE_ENV not set to production), @react-router/serve purges the require cache for every request to ensure the latest code is run. This has two important implications:

    1. Module scope values are reset: Any variables declared in the module scope (like a Map used for caching) will be re-initialized on every request. To preserve cache in development, you must set up a singleton in your server.

    2. Module side effects persist: Code that runs immediately upon import (e.g., setInterval) will still execute and persist.

    If your application relies heavily on module side effects or persistent module-level state, consider using a custom @react-router/express server combined with a tool like pm2-dev or nodemon to handle restarts.

    // WARNING: This cache will be reset for every request in development
    const cache = new Map();
    
    export async function loader({ params }: Route.LoaderArgs) {
      if (cache.has(params.foo)) {
        return cache.get(params.foo);
      }
      // ...
    }
    
    // WARNING: This side effect will persist and may cause issues
    setInterval(() => {
      console.log(Date.now());
    }, 1000);
  5. Manage accessibility during client-side routing

    main

    When using <Scripts />, React Router takes control of routing and prevents the browser's default behavior. Because React Router does not make assumptions about your UI during route transitions, you must manually handle certain accessibility patterns to support keyboard and screen-reader users:

    • Focus management: Ensure a logical element (like a heading or a skip link) receives focus when the route changes so keyboard users aren't left in a 'focus vacuum'.
    • Live-region announcements: Use ARIA live regions to announce route changes or transition states (like loading indicators) to screen-reader users, ensuring they are aware that the content has updated.
  6. How Hot Module Replacement (HMR) works in React Router

    main

    Hot Module Replacement (HMR) allows you to update modules in your application without a full page reload, preserving browser state (like form inputs or modal visibility) across updates. React Router supports HMR when using Vite.

    React Router uses React Fast Refresh to handle hot updates specifically for exported React components. While the React Router Vite plugin manages route-specific exports (like loader, action, meta, etc.) to ensure they are HMR-compatible, other types of code changes may trigger a full page reload.

    /* HMR is enabled when using React Router with Vite */
  7. Understand Server vs Client Middleware

    main

    React Router distinguishes between middleware that runs on the server and middleware that runs in the browser.

    Server Middleware

    • Environment: Runs on the server in Framework mode.
    • Triggers: Runs for HTML Document requests (GET /route) and .data requests (GET /route.data) for client-side navigations/fetchers.
    • Behavior: Operates on an HTTP Request and returns an HTTP Response via the next() function. This allows you to inspect or modify the response (e.g., setting headers) as it bubbles up the chain.
    • Constraint: Do not modify the Response body; only read/set status and headers.

    Client Middleware

    • Environment: Runs in the browser.
    • Triggers: Runs on every client-side navigation and fetcher call, regardless of whether a loader exists.
    • Behavior: There is no HTTP Request or Response. Instead, await next() returns the results of the active dataStrategy (a Record<string, DataStrategyResult> keyed by route ID). This allows you to perform post-processing based on the outcome of loaders/actions (e.g., a CMS redirect on a 404).
    • Constraint: Treat the returned value from next() as read-only. It represents the data for the navigation, which should be driven by loaders/actions, not middleware.
    // Server Middleware Example
    async function serverMiddleware({ request }, next) {
      console.log(request.method, request.url);
      let response = await next();
      console.log(response.status, request.method, request.url);
      return response;
    }
    
    // Client Middleware Example
    async function clientMiddleware({ request }, next) {
      console.log(request.method, request.url);
      await next();
      console.log(`Finished ${request.method} ${request.url}`);
    }
  8. How nested error boundaries work

    main

    When an error is thrown in a route, React Router renders the closest ErrorBoundary defined in the route hierarchy.

    If a route does not have its own ErrorBoundary, the error bubbles up to the parent route's boundary, and so on, until a boundary is found or it reaches the root.

    Example Hierarchy (Data Mode)

    • /app (has AppErrorBoundary)
      • /app/invoices (no boundary)
        • /app/invoices/:id (has InvoiceErrorBoundary)
          • /app/invoices/:id/payments (no boundary)

    Resolution Logic:

    • Error in App $\rightarrow$ AppErrorBoundary renders.
    • Error in Invoices $\rightarrow$ AppErrorBoundary renders.
    • Error in Invoice $\rightarrow$ InvoiceErrorBoundary renders.
    • Error in Payments $\rightarrow$ InvoiceErrorBoundary renders.
  9. How React Router manages network concurrency

    main

    React Router automates network management by mirroring and expanding upon standard browser behaviors to handle race conditions and interruptions.

    Key Concepts

    • Revalidation: After any form submission, React Router automatically fetches fresh data from the associated loaders to ensure the UI stays in sync with the server.
    • Link Navigation: When a user clicks a link, React Router initiates fetch requests for the target URL's loaders. If a new navigation occurs before the current one completes, React Router cancels the previous fetch requests to prioritize the latest intent.
    • Form Submission: If a form is submitted while a previous submission is still in flight, React Router cancels the original fetch requests and waits for the latest submission to complete before triggering revalidation.
    • Concurrent Requests with useFetcher: Unlike standard navigation, useFetcher allows multiple requests to be in flight simultaneously. React Router manages these by committing data to the UI as soon as it arrives, but it will discard data from an earlier request if a subsequent request's revalidation completes first. This prevents stale data from overwriting newer information.

    Handling Stale Data in Rare Infrastructure Conditions

    While React Router cancels requests in the browser, it cannot stop a request that has already reached the server. In extremely rare cases of inconsistent infrastructure, a canceled request might reach the server and modify data after a newer submission's revalidation has already landed.

    Mitigation Strategy: If your infrastructure is prone to this, include timestamps with your form submissions and implement server-side logic to ignore submissions that are older than the current state.

  10. Understand revalidation behavior for `fetcher.load`

    main

    Unlike standard route navigations, fetcher.load calls target a specific URL and do not trigger revalidations based on route or search parameter changes.

    fetcher.load only triggers a revalidation in two scenarios:

    1. After an action submission occurs.
    2. When an explicit revalidation is requested via the useRevalidator hook.
  11. Implement nested routes and layouts

    main

    Nested routing couples URL segments to a component hierarchy. If a filename before a . matches an existing route, the new file becomes a child route that renders inside the parent's <Outlet />. It is common practice to include an _index.tsx file within a nested directory to provide content for the parent's base URL.

    app/routes/concerts.tsx        // Parent Layout
    app/routes/concerts._index.tsx // Renders in Concerts outlet at /concerts
    app/routes/concerts.$city.tsx  // Renders in Concerts outlet at /concerts/:city