Skip to content

Update dependency remix-run/react-router to v7 #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

renovate[bot]
Copy link
Contributor

@renovate renovate bot commented Nov 25, 2024

This PR contains the following updates:

Package Update Change
remix-run/react-router major 6.28.2 -> 7.5.1

Release Notes

remix-run/react-router (remix-run/react-router)

v7.5.1

Compare Source

Date: 2025-04-17

Patch Changes
  • react-router - When using the object-based route.lazy API, the HydrateFallback and hydrateFallbackElement properties are now skipped when lazy loading routes after hydration (#​13376)

    • If you move the code for these properties into a separate file, since the hydrate properties were unused already (if the route wasn't present during hydration), you can avoid downloading them at all. For example:

      createBrowserRouter([
        {
          path: "/show/:showId",
          lazy: {
            loader: async () => (await import("./show.loader.js")).loader,
            Component: async () =>
              (await import("./show.component.js")).Component,
            HydrateFallback: async () =>
              (await import("./show.hydrate-fallback.js")).HydrateFallback,
          },
        },
      ]);
  • react-router - Fix single fetch bug where no revalidation request would be made when navigating upwards to a reused parent route (#​13253)

  • react-router - Properly revalidate pre-rendered paths when param values change when using ssr:false + prerender configs (#​13380)

  • react-router - Fix pre-rendering when a loader returns a redirect (#​13365)

  • react-router - Do not automatically add null to staticHandler.query() context.loaderData if routes do not have loaders (#​13223)

    • This was a Remix v2 implementation detail inadvertently left in for React Router v7
    • Now that we allow returning undefined from loaders, our prior check of loaderData[routeId] !== undefined was no longer sufficient and was changed to a routeId in loaderData check - these null values can cause issues for this new check
    • ⚠️ This could be a "breaking bug fix" for you if you are doing manual SSR with createStaticHandler()/<StaticRouterProvider>, and using context.loaderData to control <RouterProvider> hydration behavior on the client
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - Add better error messaging when getLoadContext is not updated to return a Map (#​13242)
  • react-router - Update context type for LoaderFunctionArgs/ActionFunctionArgs when middleware is enabled (#​13381)
  • react-router - Add a new unstable_runClientMiddleware argument to dataStrategy to enable middleware execution in custom dataStrategy implementations (#​13395)
  • react-router - Add support for the new unstable_shouldCallHandler/unstable_shouldRevalidateArgs APIs in dataStrategy (#​13253)

Full Changelog: v7.5.0...v7.5.1

v7.5.0

Compare Source

Date: 2025-04-04

What's Changed
route.lazy Object API

We've introduced a new route.lazy API which gives you more granular control over the lazy loading of route properties that you could not achieve with the route.lazy() function signature. This is useful for Framework mode and performance-critical library mode applications.

createBrowserRouter([
  {
    path: "/show/:showId",
    lazy: {
      loader: async () => (await import("./show.loader.js")).loader,
      action: async () => (await import("./show.action.js")).action,
      Component: async () => (await import("./show.component.js")).Component,
    },
  },
]);

⚠️ This is a breaking change if you have adopted the route.unstable_lazyMiddleware API which has been removed in favor of route.lazy.unstable_middleware. See the Unstable Changes section below for more information.

Minor Changes
  • react-router - Add granular object-based API for route.lazy to support lazy loading of individual route properties (#​13294)
Patch Changes
  • @react-router/dev - Update optional wrangler peer dependency range to support wrangler v4 (#​13258)
  • @react-router/dev - Reinstate dependency optimization in the child compiler to fix depsOptimizer is required in dev mode errors when using vite-plugin-cloudflare and importing Node.js builtins (#​13317)
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - Introduce future.unstable_subResourceIntegrity flag that enables generation of an importmap with integrity for the scripts that will be loaded by the browser (#​13163)
  • react-router - Remove support for the route.unstable_lazyMiddleware property (#​13294)
    • In order to lazily load middleware, you can use the new object-based route.lazy.unstable_middleware API
  • @react-router/dev - When future.unstable_viteEnvironmentApi is enabled, ensure critical CSS in development works when using a custom Vite base has been configured (#​13305)
Changes by Package

Full Changelog: v7.4.1...v7.5.0

v7.4.1

Compare Source

Date: 2025-03-28

Security Notice

Fixed a security vulnerability that allowed URL manipulation and potential cache pollution via the Host and X-Forwarded-Host headers due to inadequate port sanitization.

Patch Changes
  • react-router - Dedupe calls to route.lazy functions (#​13260)
  • @react-router/dev - Fix path in prerender error messages (#​13257)
  • @react-router/dev - Fix typegen for virtual modules when moduleDetection is set to force (#​13267)
  • @react-router/express - Better validation of x-forwarded-host header to prevent potential security issues (#​13309)
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - Fix types on unstable_MiddlewareFunction to avoid type errors when a middleware doesn't return a value (#​13311)
  • react-router - Add support for route.unstable_lazyMiddleware function to allow lazy loading of middleware logic (#​13210)
    • ⚠️ We do not recommend adoption of this API currently as we are likely going to change it prior to the stable release of middleware
    • ⚠️ This may be a breaking change if your app is currently returning unstable_middleware from route.lazy
    • The route.unstable_middleware property is no longer supported in the return value from route.lazy
    • If you want to lazily load middleware, you must use route.unstable_lazyMiddleware
  • @react-router/dev - When both future.unstable_middleware and future.unstable_splitRouteModules are enabled, split unstable_clientMiddleware route exports into separate chunks when possible (#​13210)
  • @react-router/dev - Improve performance of future.unstable_middleware by ensuring that route modules are only blocking during the middleware phase when the unstable_clientMiddleware has been defined (#​13210)

Full Changelog: v7.4.0...v7.4.1

v7.4.0

Compare Source

Date: 2025-03-19

Minor Changes
  • @react-router/dev - Generate types for virtual:react-router/server-build module (#​13152)
Patch Changes
  • react-router - Fix root loader data on initial load redirects in SPA mode (#​13222)
  • react-router - Load ancestor pathless/index routes in lazy route discovery for upwards non-eager-discovery routing (#​13203)
  • react-router - Fix shouldRevalidate behavior for clientLoader-only routes in ssr:true apps (#​13221)
  • @react-router/dev - Fix conflicts with other Vite plugins that use the configureServer and/or configurePreviewServer hooks (#​13184)
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - If a middleware throws an error, ensure we only bubble the error itself via next() and are no longer leaking the MiddlewareError implementation detail (#​13180)
    • ⚠️ This may be a breaking change if you are catch-ing errors thrown by the next() function in your middlewares
  • react-router - Fix RequestHandler loadContext parameter type when middleware is enabled (#​13204)
  • react-router - Update Route.unstable_MiddlewareFunction to have a return value of Response | undefined instead of Response | void (#​13199)
  • @react-router/dev - When future.unstable_splitRouteModules is set to "enforce", allow both splittable and unsplittable root route exports since it's always in a single chunk (#​13238)
  • @react-router/dev - When future.unstable_viteEnvironmentApi is enabled, allow plugins that override the default SSR environment (such as @cloudflare/vite-plugin) to be placed before or after the React Router plugin (#​13183)
Changes by Package

Full Changelog: v7.3.0...v7.4.0

v7.3.0

Compare Source

Date: 2025-03-06

Minor Changes
  • Add fetcherKey as a parameter to patchRoutesOnNavigation (#​13061)
Patch Changes
  • react-router - Detect and handle manifest-skew issues on new deploys during active sessions (#​13061)
    • In framework mode, Lazy Route Discovery will now detect manifest version mismatches in active sessions after a new deploy
    • On navigations to undiscovered routes, this mismatch will trigger a document reload of the destination path
    • On fetcher calls to undiscovered routes, this mismatch will trigger a document reload of the current path
  • react-router - Skip resource route flow in dev server in SPA mode (#​13113)
  • react-router - Fix single fetch _root.data requests when a basename is used (#​12898)
  • react-router - Fix types for loaderData and actionData that contained Records (#​13139)
    • ⚠️ This is a breaking change for users who have already adopted unstable_SerializesTo - see the note in the Unstable Changes section below for more information
  • @react-router/dev - Fix support for custom client build.rollupOptions.output.entryFileNames (#​13098)
  • @react-router/dev - Fix usage of prerender option when serverBundles option has been configured or provided by a preset, e.g. vercelPreset from @vercel/react-router (#​13082)
  • @react-router/dev - Fix support for custom build.assetsDir (#​13077)
  • @react-router/dev - Remove unused dependencies (#​13134)
  • @react-router/dev - Stub all routes except root in "SPA Mode" server builds to avoid issues when route modules or their dependencies import non-SSR-friendly modules (#​13023)
  • @react-router/dev - Remove unused Vite file system watcher (#​13133)
  • @react-router/dev - Fix support for custom SSR build input when serverBundles option has been configured (#​13107)
    • ⚠️ Note that for consumers using the future.unstable_viteEnvironmentApi and serverBundles options together, hyphens are no longer supported in server bundle IDs since they also need to be valid Vite environment names.
  • @react-router/dev - Fix dev server when using HTTPS by stripping HTTP/2 pseudo headers from dev server requests (#​12830)
  • @react-router/dev - Lazy load Cloudflare platform proxy on first dev server request when using the cloudflareDevProxy Vite plugin to avoid creating unnecessary workerd processes (#​13016)
  • @react-router/dev - Fix duplicated entries in typegen for layout routes and their corresponding index route (#​13140)
  • @react-router/express - Update express peerDependency to include v5 (https://github.com/remix-run/react-router/pull/13064) (#​12961)
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - Add context support to client side data routers (unstable) (#​12941)
  • react-router - Support middleware on routes (unstable) (#​12941)
  • @react-router/dev - Fix errors with future.unstable_viteEnvironmentApi when the ssr environment has been configured by another plugin to be a custom Vite.DevEnvironment rather than the default Vite.RunnableDevEnvironment (#​13008)
  • @react-router/dev - When future.unstable_viteEnvironmentApi is enabled and the ssr environment has optimizeDeps.noDiscovery disabled, define optimizeDeps.entries and optimizeDeps.include (#​13007)
Client-side context (unstable)

Your application clientLoader/clientAction functions (or loader/action in library mode) will now receive a context parameter on the client. This is an instance of unstable_RouterContextProvider that you use with type-safe contexts (similar to React.createContext) and is most useful with the corresponding unstable_clientMiddleware API:

import { unstable_createContext } from "react-router";

type User = {
  /*...*/
};

const userContext = unstable_createContext<User>();

const sessionMiddleware: Route.unstable_ClientMiddlewareFunction = async ({
  context,
}) => {
  let user = await getUser();
  context.set(userContext, user);
};

export const unstable_clientMiddleware = [sessionMiddleware];

export function clientLoader({ context }: Route.ClientLoaderArgs) {
  let user = context.get(userContext);
  let profile = await getProfile(user.id);
  return { profile };
}

Similar to server-side requests, a fresh context will be created per navigation (or fetcher call). If you have initial data you'd like to populate in the context for every request, you can provide an unstable_getContext function at the root of your app:

  • Library mode - createBrowserRouter(routes, { unstable_getContext })
  • Framework mode - <HydratedRouter unstable_getContext>

This function should return an value of type unstable_InitialContext which is a Map<unstable_RouterContext, unknown> of context's and initial values:

const loggerContext = unstable_createContext<(...args: unknown[]) => void>();

function logger(...args: unknown[]) {
  console.log(new Date.toISOString(), ...args);
}

function unstable_getContext() {
  let map = new Map();
  map.set(loggerContext, logger);
  return map;
}
Middleware (unstable)

Middleware is implemented behind a future.unstable_middleware flag. To enable, you must enable the flag and the types in your react-router.config.ts file:

import type { Config } from "@&#8203;react-router/dev/config";
import type { Future } from "react-router";

declare module "react-router" {
  interface Future {
    unstable_middleware: true; // 👈 Enable middleware types
  }
}

export default {
  future: {
    unstable_middleware: true, // 👈 Enable middleware
  },
} satisfies Config;

⚠️ Middleware is unstable and should not be adopted in production. There is at least one known de-optimization in route module loading for clientMiddleware that we will be addressing this before a stable release.

⚠️ Enabling middleware contains a breaking change to the context parameter passed to your loader/action functions - see below for more information.

Once enabled, routes can define an array of middleware functions that will run sequentially before route handlers run. These functions accept the same parameters as loader/action plus an additional next parameter to run the remaining data pipeline. This allows middlewares to perform logic before and after handlers execute.

// Framework mode
export const unstable_middleware = [serverLogger, serverAuth]; // server
export const unstable_clientMiddleware = [clientLogger]; // client

// Library mode
const routes = [
  {
    path: "/",
    // Middlewares are client-side for library mode SPA's
    unstable_middleware: [clientLogger, clientAuth],
    loader: rootLoader,
    Component: Root,
  },
];

Here's a simple example of a client-side logging middleware that can be placed on the root route:

const clientLogger: Route.unstable_ClientMiddlewareFunction = async (
  { request },
  next
) => {
  let start = performance.now();

  // Run the remaining middlewares and all route loaders
  await next();

  let duration = performance.now() - start;
  console.log(`Navigated to ${request.url} (${duration}ms)`);
};

Note that in the above example, the next/middleware functions don't return anything. This is by design as on the client there is no "response" to send over the network like there would be for middlewares running on the server. The data is all handled behind the scenes by the stateful router.

For a server-side middleware, the next function will return the HTTP Response that React Router will be sending across the wire, thus giving you a chance to make changes as needed. You may throw a new response to short circuit and respond immediately, or you may return a new or altered response to override the default returned by next().

const serverLogger: Route.unstable_MiddlewareFunction = async (
  { request, params, context },
  next
) => {
  let start = performance.now();

  // 👇 Grab the response here
  let res = await next();

  let duration = performance.now() - start;
  console.log(`Navigated to ${request.url} (${duration}ms)`);

  // 👇 And return it here (optional if you don't modify the response)
  return res;
};

You can throw a redirect from a middleware to short circuit any remaining processing:

import { sessionContext } from "../context";
const serverAuth: Route.unstable_MiddlewareFunction = (
  { request, params, context },
  next
) => {
  let session = context.get(sessionContext);
  let user = session.get("user");
  if (!user) {
    session.set("returnTo", request.url);
    throw redirect("/login", 302);
  }
};

Note that in cases like this where you don't need to do any post-processing you don't need to call the next function or return a Response.

Here's another example of using a server middleware to detect 404s and check the CMS for a redirect:

const redirects: Route.unstable_MiddlewareFunction = async ({
  request,
  next,
}) => {
  // attempt to handle the request
  let res = await next();

  // if it's a 404, check the CMS for a redirect, do it last
  // because it's expensive
  if (res.status === 404) {
    let cmsRedirect = await checkCMSRedirects(request.url);
    if (cmsRedirect) {
      throw redirect(cmsRedirect, 302);
    }
  }

  return res;
};

For more information on the middleware API/design, please see the decision doc.

Middleware context parameter

When middleware is enabled, your application will use a different type of context parameter in your loaders and actions to provide better type safety. Instead of AppLoadContext, context will now be an instance of ContextProvider that you can use with type-safe contexts (similar to React.createContext):

import { unstable_createContext } from "react-router";
import { Route } from "./+types/root";
import type { Session } from "./sessions.server";
import { getSession } from "./sessions.server";

let sessionContext = unstable_createContext<Session>();

const sessionMiddleware: Route.unstable_MiddlewareFunction = ({
  context,
  request,
}) => {
  let session = await getSession(request);
  context.set(sessionContext, session);
  //                          ^ must be of type Session
};

// ... then in some downstream middleware
const loggerMiddleware: Route.unstable_MiddlewareFunction = ({
  context,
  request,
}) => {
  let session = context.get(sessionContext);
  //  ^ typeof Session
  console.log(session.get("userId"), request.method, request.url);
};

// ... or some downstream loader
export function loader({ context }: Route.LoaderArgs) {
  let session = context.get(sessionContext);
  let profile = await getProfile(session.get("userId"));
  return { profile };
}

If you are using a custom server with a getLoadContext function, the return value for initial context values passed from the server adapter layer is no longer an object and should now return an unstable_InitialContext (Map<RouterContext, unknown>):

let adapterContext = unstable_createContext<MyAdapterContext>();

function getLoadContext(req, res): unstable_InitialContext {
  let map = new Map();
  map.set(adapterContext, getAdapterContext(req));
  return map;
}
unstable_SerializesTo

unstable_SerializesTo added a way to register custom serialization types in Single Fetch for other library and framework authors like Apollo. It was implemented with branded type whose branded property that was made optional so that casting arbitrary values was easy:

// without the brand being marked as optional
let x1 = 42 as unknown as unstable_SerializesTo<number>;
//          ^^^^^^^^^^

// with the brand being marked as optional
let x2 = 42 as unstable_SerializesTo<number>;

However, this broke type inference in loaderData and actionData for any Record types as those would now (incorrectly) match unstable_SerializesTo. This affected all users, not just those that depended on unstable_SerializesTo. To fix this, the branded property of unstable_SerializesTo is marked as required instead of optional.

For library and framework authors using unstable_SerializesTo, you may need to add as unknown casts before casting to unstable_SerializesTo.

Changes by Package

Full Changelog: v7.2.0...v7.3.0

v7.2.0

Compare Source

Date: 2025-02-18

What's Changed
Type-safe href utility

In framework mode, we now provide you with a fully type-safe href utility to give you all the warm and fuzzy feelings of path auto-completion and param validation for links in your application:

import { href } from "react-router";

export default function Component() {
  const link = href("/blog/:slug", { slug: "my-first-post" });
  //                ^ type-safe!     ^ Also type-safe!

  return (
    <main>
      <Link to={href("/products/:id", { id: "asdf" })} />
      <NavLink to={href("/:lang?/about", { lang: "en" })} />
    </main>
  );
}

You'll now get type errors if you pass a bad path value or a bad param value:

const badPath = href("/not/a/valid/path");
//                   ^ Error!

const badParam = href("/blog/:slug", { oops: "bad param" });
//                                     ^ Error!
Prerendering with a SPA Fallback

This release enhances the ability to use a combination of pre-rendered paths alongside other paths that operate in "SPA Mode" when pre-rendering with ssr:false.

  • If you specify ssr:false without a prerender config, this is considered "SPA Mode" and the generated index.html file will only render down to the root route and will be able to hydrate for any valid application path
  • If you specify ssr:false with a prerender config but do not include the / path (i.e., prerender: ['/blog/post']), then we still generate a "SPA Mode" index.html file that can hydrate for any path in the application
  • If you specify ssr:false and include the / path in your prerender config, the generated index.html file will be specific to the root index route, so we will now also generate a separate "SPA Mode" file in __spa-fallback.html that you can serve/hydrate for non-prerendered paths

For more info, see the Pre-rendering docs for more info.

Allow a root loader in SPA Mode

SPA Mode used to prohibit the use of loaders in all routes so that we could hydrate for any path in the application. However, because the root route is always rendered at build time, we can lift this restriction for the root route.

In order to use your build-time loader data during pre-rendering, we now also expose the loaderData as an optional prop for the HydrateFallback component on routes:

  • This will be defined so long as the HydrateFallback is rendering because children routes are loading
  • This will be undefined if the HydrateFallback is rendering because the route itself has it's own hydrating clientLoader
    • In SPA mode, this will allow you to render loader root data into the SPA Mode HTML file
Minor Changes
  • react-router - New type-safe href utility that guarantees links point to actual paths in your app (#​13012)
  • @react-router/dev - Generate a "SPA fallback" HTML file when pre-rendering the / route with ssr:false (#​12948)
  • @react-router/dev - Allow a loader in the root route in SPA mode because it can be called/server-rendered at build time (#​12948)
    • Route.HydrateFallbackProps now also receives loaderData
Patch Changes
  • react-router - Disable Lazy Route Discovery for all ssr:false apps and not just "SPA Mode" because there is no runtime server to serve the search-param-configured __manifest requests (#​12894)
    • We previously only disabled this for "SPA Mode" but we realized it should apply to all ssr:false apps
    • In those prerender scenarios we would pre-render the /__manifest file but that makes some unnecessary assumptions about the static file server behaviors
  • react-router - Don't apply Single Fetch revalidation de-optimization when in SPA mode since there is no server HTTP request (#​12948)
  • react-router - Properly handle revalidations to across a pre-render/SPA boundary (#​13021)
    • In "hybrid" applications where some routes are pre-rendered and some are served from a SPA fallback, we need to avoid making .data requests if the path wasn't pre-rendered because the request will 404
    • We don't know all the pre-rendered paths client-side, however:
      • All loader data in ssr:false mode is static because it's generated at build time
      • A route must use a clientLoader to do anything dynamic
      • Therefore, if a route only has a loader and not a clientLoader, we disable revalidation by default because there is no new data to retrieve
      • We short circuit and skip single fetch .data request logic if there are no server loaders with shouldLoad=true in our single fetch dataStrategy
      • This ensures that the route doesn't cause a .data request that would 404 after a submission
  • react-router - Align dev server behavior with static file server behavior when ssr:false is set (#​12948)
    • When no prerender config exists, only SSR down to the root HydrateFallback (SPA Mode)
    • When a prerender config exists but the current path is not pre-rendered, only SSR down to the root HydrateFallback (SPA Fallback)
    • Return a 404 on .data requests to non-pre-rendered paths
  • react-router - Improve prefetch performance of CSS side effects in framework mode (#​12889)
  • react-router - Properly handle interrupted manifest requests in lazy route discovery (#​12915)
  • @react-router/dev - Handle custom envDir in Vite config (#​12969)
  • @react-router/dev - Fix CLI parsing to allow argument-less npx react-router usage (#​12925)
  • @react-router/dev - Skip action-only resource routes when using prerender:true (#​13004)
  • @react-router/dev - Enhance invalid export detection when using ssr:false (#​12948)
    • headers/action functions are prohibited in all routes with ssr:false because there will be no runtime server on which to run them
    • loader functions are more nuanced and depend on whether a given route is prerendered
      • When using ssr:false without a prerender config, only the root route can have a loader
      • When using ssr:false with a prerender config, only routes matched by a prerender path can have a loader
  • @react-router/dev - Error at build time in ssr:false + prerender apps for the edge case scenario of: (#​13021)
    • A parent route has only a loader (does not have a clientLoader)
    • The parent route is pre-rendered
    • The parent route has children routes which are not prerendered
    • This means that when the child paths are loaded via the SPA fallback, the parent won't have any loaderData because there is no server on which to run the loader
    • This can be resolved by either adding a parent clientLoader or pre-rendering the child paths
    • If you add a clientLoader, calling the serverLoader() on non-prerendered paths will throw a 404
  • @react-router/dev - Limit prerendered resource route .data files to only the target route (#​13004)
  • @react-router/dev - Fix pre-rendering of binary files (#​13039)
  • @react-router/dev - Fix typegen for repeated params (#​13012)
    • In React Router, path parameters are keyed by their name, so for a path pattern like /a/:id/b/:id?/c/:id, the last :id will set the value for id in useParams and the params prop
      • For example, /a/1/b/2/c/3 will result in the value { id: 3 } at runtime
    • Previously, generated types for params incorrectly modeled repeated params with an array
      • For example, /a/1/b/2/c/3 generated a type like { id: [1,2,3] }.
    • To be consistent with runtime behavior, the generated types now correctly model the "last one wins" semantics of path parameters.
      • For example, /a/1/b/2/c/3 now generates a type like { id: 3 }.
  • @react-router/dev - Fix path to load package.json for react-router --version (#​13012)
Unstable Changes

⚠️ Unstable features are not recommended for production use

  • react-router - Add unstable_SerializesTo brand type for library authors to register types serializable by React Router's streaming format (turbo-stream) (#​12264)
  • @react-router/dev - Add unstable support for splitting route modules in framework mode via future.unstable_splitRouteModules (#​11871)
  • @react-router/dev - Add future.unstable_viteEnvironmentApi flag to enable experimental Vite Environment API support (#​12936)
Split Route Modules (unstable)

⚠️ This feature is currently unstable, enabled by the future.unstable_splitRouteModules flag. We’d love any interested users to play with it locally and provide feedback, but we do not recommend using it in production yet.

If you do choose to adopt this flag in production, please ensure you do sufficient testing against your production build to ensure that the optimization is working as expected.

One of the conveniences of the Route Module API is that everything a route needs is in a single file. Unfortunately this comes with a performance cost in some cases when using the clientLoader, clientAction, and HydrateFallback APIs.

As a basic example, consider this route module:

import { MassiveComponent } from "~/components";

export async function clientLoader() {
  return await fetch("https://example.com/api").then((response) =>
    response.json()
  );
}

export default function Component({ loaderData }) {
  return <MassiveComponent data={loaderData} />;
}

In this example we have a minimal clientLoader export that makes a basic fetch call, whereas the default component export is much larger. This is a problem for performance because it means that if we want to navigate to this route client-side, the entire route module must be downloaded before the client loader can start running.

To visualize this as a timeline:

In the following timeline diagrams, different characters are used within the Route Module bars to denote the different Route Module APIs being exported.

Get Route Module:  |--=======|
Run clientLoader:            |-----|
Render:                            |-|

Instead, we want to optimize this to the following:

Get clientLoader:  |--|
Get Component:     |=======|
Run clientLoader:     |-----|
Render:                     |-|

To achieve this optimization, React Router will split the route module into multiple smaller modules during the production build process. In this case, we'll end up with two separate virtual modules — one for the client loader and one for the component and its dependencies.

export async function clientLoader() {
  return await fetch("https://example.com/api").then((response) =>
    response.json()
  );
}
import { MassiveComponent } from "~/components";

export default function Component({ loaderData }) {
  return <MassiveComponent data={loaderData} />;
}

💡 This optimization is automatically applied in framework mode, but you can also implement it in library mode via route.lazy and authoring your route in multiple files as covered in our blog post on lazy loading route modules.

Now that these are available as separate modules, the client loader and the component can be downloaded in parallel. This means that the client loader can be executed as soon as it's ready without having to wait for the component.

This optimization is even more pronounced when more Route Module APIs are used. For example, when using clientLoader, clientAction and HydrateFallback, the timeline for a single route module during a client-side navigation might look like this:

Get Route Module:     |--~~++++=======|
Run clientLoader:                     |-----|
Render:                                     |-|

This would instead be optimized to the following:

Get clientLoader:     |--|
Get clientAction:     |~~|
Get HydrateFallback:  SKIPPED
Get Component:        |=======|
Run clientLoader:        |-----|
Render:                        |-|

Note that this optimization only works when the Route Module APIs being split don't share code within the same file. For example, the following route module can't be split:

import { MassiveComponent } from "~/components";

const shared = () => console.log("hello");

export async function clientLoader() {
  shared();
  return await fetch("https://example.com/api").then((response) =>
    response.json()
  );
}

export default function Component({ loaderData }) {
  shared();
  return <MassiveComponent data={loaderData} />;
}

This route will still work, but since both the client loader and the component depend on the shared function defined within the same file, it will be de-optimized into a single route module.

To avoid this, you can extract any code shared between exports into a separate file. For example:

export const shared = () => console.log("hello");

You can then import this shared code in your route module without triggering the de-optimization:

import { MassiveComponent } from "~/components";
import { shared } from "./shared";

export async function clientLoader() {
  shared();
  return await fetch("https://example.com/api").then((response) =>
    response.json()
  );
}

export default function Component({ loaderData }) {
  shared();
  return <MassiveComponent data={loaderData} />;
}

Since the shared code is in its own module, React Router is now able to split this route module into two separate virtual modules:

import { shared } from "./shared";

export async function clientLoader() {
  shared();
  return await fetch("https://example.com/api").then((response) =>
    response.json()
  );
}
import { MassiveComponent } from "~/components";
import { shared } from "./shared";

export default function Component({ loaderData }) {
  shared();
  return <MassiveComponent data={loaderData} />;
}

If your project is particularly performance sensitive, you can set the unstable_splitRouteModules future flag to "enforce":

export default {
  future: {
    unstable_splitRouteModules: "enforce",
  },
};

This setting will raise an error if any route modules can't be split:

Error splitting route module: routes/example/route.tsx

- clientLoader

This export could not be split into its own chunk because it shares code with other exports. You should extract any shared code into its own module and then import it within the route module.
Changes by Package

Full Changelog: v7.1.5...v7.2.0

v7.1.5

Compare Source

Date: 2025-01-31

Patch Changes
  • react-router - Fix regression introduced in 7.1.4 via #​12800 that caused issues navigating to hash routes inside splat routes for applications using Lazy Route Discovery (patchRoutesOnNavigation) (#​12927)

Full Changelog: v7.1.4...v7.1.5

v7.1.4

Compare Source

Date: 2025-01-30

Patch Changes
  • @react-router/dev - Properly resolve Windows file paths to scan for Vite's dependency optimization when using the unstable_optimizeDeps future flag (#​12637)
  • @react-router/dev - Fix prerendering when using a custom server - previously we ended up trying to import the users custom server when we actually want to import the virtual server build module (#​12759)
  • react-router - Properly handle status codes that cannot have a body in single fetch responses (204, etc.) (#​12760)
  • react-router - Properly bubble headers as errorHeaders when throwing a data() result (#​12846)
    • Avoid duplication of Set-Cookie headers if also returned from headers
  • react-router - Stop erroring on resource routes that return raw strings/objects and instead serialize them as text/plain or application/json responses (#​12848)
    • This only applies when accessed as a resource route without the .data extension
    • When accessed from a Single Fetch .data request, they will still be encoded via turbo-stream
  • react-router - Optimize Lazy Route Discovery path discovery to favor a single querySelectorAll call at the body level instead of many calls at the sub-tree level (#​12731)
  • react-router - Optimize route matching by skipping redundant matchRoutes calls when possible (#​12800, #​12882)
  • react-router - Internal reorg to clean up some duplicated route module types (#​12799)

Full Changelog: v7.1.3...v7.1.4

v7.1.2

Compare Source

Date: 2025-01-16

Patch Changes
  • react-router - Fix issue with fetcher data cleanup in the data layer on fetcher unmount (#​12681)
  • react-router - Do not rely on symbol for filtering out redirect responses from loader data (#​12694)
    • Previously, some projects were getting type checking errors like:
      error TS4058: Return type of exported function has or is using name 'redirectSymbol' from external module "node_modules/..." but cannot be named.
    • Now that symbols are not used for the redirect response type, these errors should no longer be present
  • @react-router/dev - Fix default external conditions in Vite v6 (#​12644)
    • This fixes resolution issues with certain npm packages
  • @react-router/dev - Fix mismatch in prerendering html/data files when path is missing a leading slash (#​12684)
  • @react-router/dev - Use module-sync server condition when enabled in the runtime. This fixes React context mismatches (e.g. useHref() may be used only in the context of a <Router> component.) during development on Node 22.10.0+ when using libraries that have a peer dependency on React Router (#​12729)
  • @react-router/dev - Fix react-refresh source maps (#​12686)

Full Changelog: v7.1.1...v7.1.2

v7.1.1

Compare Source

Date: 2024-12-23

Patch Changes
  • @react-router/dev - Fix for a crash when optional args are passed to the CLI (#​12609)

Full Changelog: v7.1.0...v7.1.1

v7.1.0

Compare Source

Date: 2024-12-20

Minor Changes
Patch Changes
  • react-router - Throw unwrapped Single Fetch redirect to align with pre-Single Fetch behavior (#​12506)
  • react-router - Ignore redirects when inferring loader data types (#​12527)
  • react-router - Remove <Link prefetch> warning which suffers from false positives in a lazy route discovery world (#​12485)
  • create-react-router - Fix missing fs-extra dependency (#​12556)
  • @react-router/dev/@react-router/serve - Properly initialize NODE_ENV if not already set for compatibility with React 19 (#​12578)
  • @react-router/dev - Remove the leftover/unused abortDelay prop from ServerRouter and update the default entry.server.tsx to use the new streamTimeout value for Single Fetch (#​12478)
    • The abortDelay functionality was removed in v7 as it was coupled to the defer implementation from Remix v2, but this removal of this prop was missed
    • If you were still using this prop in your entry.server file, it's likely your app is not aborting streams as you would expect and you will need to adopt the new streamTimeout value introduced with Single Fetch
  • @react-router/fs-routes - Throw error in flatRoutes if routes directory is missing (#​12407)
Changes by Package

Configuration

📅 Schedule: Branch creation - Between 12:00 AM and 03:59 AM, only on Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch 3 times, most recently from ace908f to 49b68b6 Compare December 3, 2024 07:00
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch 3 times, most recently from a80b66e to f489cb2 Compare December 23, 2024 17:32
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch 4 times, most recently from 8c03a7e to acc910e Compare January 20, 2025 06:48
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch 2 times, most recently from 827a3c5 to 3bba430 Compare February 1, 2025 20:56
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch from 3bba430 to eec505b Compare February 18, 2025 23:28
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch from eec505b to 30ff735 Compare March 6, 2025 23:09
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch from 30ff735 to d3177e8 Compare March 19, 2025 16:29
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch 2 times, most recently from 4b3269b to 9d68d3f Compare April 4, 2025 22:11
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch from 9d68d3f to 2a4ceb1 Compare April 12, 2025 18:29
@renovate renovate bot force-pushed the renovate/major-react-router-monorepo branch from 2a4ceb1 to a25c423 Compare April 17, 2025 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants