NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #1611 most downloaded on npm
Declarative routing for React
Last release 2 days ago
15 Sep 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
13 years old
1186 releases · first in 2014
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v780
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v780
Date: 2025-08-07
loaderData valuesEver noticed the discrepancies in loader data values handed to you by the framework? Like, we call it loaderData in your component props, but then match.data in your matches? Yeah, us too - as well as some keen-eyed React Router users who raised this in a proposal. We've added new loaderData fields alongside existing data fields in a few lingering spots to align with the loaderData naming used in the new Route.* APIs.
The biggest set of changes in 7.8.0 are to the unstable_middleware API's as we move closer to stabilizing them. If you've adopted the middleware APIs for early testing, please read the middleware changes below carefully. We hope to stabilize these soon so please let us know of any feedback you have on the API's in their current state!
react-router - Add nonce prop to Links & PrefetchPageLinks (#14048)react-router - Add loaderData arguments/properties alongside existing data arguments/properties to provide consistency and clarity between loaderData and actionData across the board (#14047)
Route.MetaArgs, Route.MetaMatch, MetaArgs, MetaMatch, Route.ComponentProps.matches, UIMatch@deprecated warnings have been added to the existing data properties to point users to new loaderData properties, in preparation for removing the data properties in a future major releasereact-router - Prevent "Did not find corresponding fetcher result" console error when navigating during a fetcher.submit revalidation (#14114)
react-router - Switch Lazy Route Discovery manifest URL generation to use a standalone URLSearchParams instance instead of URL.searchParams to avoid a major performance bottleneck in Chrome (#14084)
react-router - Adjust internal RSC usage of React.use to avoid Webpack compilation errors when using React 18 (#14113)
react-router - Remove dependency on @types/node in TypeScript declaration files (#14059)
react-router - Fix types for UIMatch to reflect that the loaderData/data properties may be undefined (#12206)
When an ErrorBoundary is being rendered, not all active matches will have loader data available, since it may have been their loader that threw to trigger the boundary
The UIMatch.data type was not correctly handing this and would always reflect the presence of data, leading to the unexpected runtime errors when an ErrorBoundary was rendered
⚠️ This may cause some type errors to show up in your code for unguarded match.data accesses - you should properly guard for undefined values in those scenarios.
// app/root.tsx
export function loader() {
someFunctionThatThrows(); // ❌ Throws an Error
return { title: "My Title" };
}
export function Layout({ children }: { children: React.ReactNode }) {
let matches = useMatches();
let rootMatch = matches[0] as UIMatch<Awaited<ReturnType<typeof loader>>>;
// ^ rootMatch.data is currently incorrectly typed here, so TypeScript does
// not complain if you do the following which throws an error at runtime:
let { title } = rootMatch.data; // 💥
return <html>...</html>;
}
@react-router/dev - Fix rename without mkdir in Vite plugin (#14105)
⚠️ Unstable features are not recommended for production use
RSC
react-router - Fix Data Mode issue where routes that return false from shouldRevalidate would be replaced by an <Outlet /> (#14071)react-router - Proxy server action side-effect redirects from actions for document and callServer requests (#14131)Middleware
react-router - Change the unstable_getContext signature on RouterProvider, HydratedRouter, and unstable_RSCHydratedRouter so that it returns an unstable_RouterContextProvider instance instead of a Map used to construct the instance internally (#14097)
unstable_getContext propreact-router - Run client middleware on client navigations even if no loaders exist (#14106)
react-router - Convert internal middleware implementations to use the new unstable_generateMiddlewareResponse API (#14103)
react-router - Ensure resource route errors go through handleError w/middleware enabled (#14078)
react-router - Propagate returned Response from server middleware if next wasn't called (#14093)
react-router - Allow server middlewares to return data() values which will be converted into a Response (#14093, #14128)
react-router - Update middleware error handling so that the next function never throws and instead handles any middleware errors at the proper ErrorBoundary and returns the Response up through the ancestor next function (#14118)
next calls in try/catch you should be able to remove thosereact-router - Bubble client-side middleware errors prior to next to the appropriate ancestor error boundary (#14138)
react-router - When middleware is enabled, make the context parameter read-only (Readonly<unstable_RouterContextProvider>) so that TypeScript will not allow you to write arbitrary fields to it in loaders, actions, or middleware. (#14097)
react-router - Rename and alter the signature/functionality of the unstable_respond API in staticHandler.query/staticHandler.queryRoute (#14103)
This only impacts users using createStaticHandler() for manual data loading during non-Framework Mode SSR
The API has been renamed to unstable_generateMiddlewareResponse for clarity
The main functional change is that instead of running the loaders/actions before calling unstable_respond and handing you the result, we now pass a query/queryRoute function as a parameter and you execute the loaders/actions inside your callback, giving you full access to pre-processing and error handling
The query version of the API now has a signature of (query: (r: Request) => Promise<StaticHandlerContext | Response>) => Promise<Response>
The queryRoute version of the API now has a signature of (queryRoute: (r: Request) => Promise<Response>) => Promise<Response>
This allows for more advanced usages such as running logic before/after calling query and direct error handling of errors thrown from query
⚠️ This is a breaking change if you've adopted the staticHandler unstable_respond API
let response = await staticHandler.query(request, {
requestContext: new unstable_RouterContextProvider(),
async unstable_generateMiddlewareResponse(query) {
try {
// At this point we've run middleware top-down so we need to call the
// handlers and generate the Response to bubble back up the middleware
let result = await query(request);
if (isResponse(result)) {
return result; // Redirects, etc.
}
return await generateHtmlResponse(result);
} catch (error: unknown) {
return generateErrorResponse(error);
}
},
});
@react-router/{architect,cloudflare,express,node} - Change the getLoadContext signature (type GetLoadContextFunction) when future.unstable_middleware is enabled so that it returns an unstable_RouterContextProvider instance instead of a Map used to construct the instance internally (#14097)
type unstable_InitialContext exportgetLoadContext docs for more informationgetLoadContext functioncreate-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.7.1...v7.8.0
One column per quarter.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v771
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v771
Date: 2025-07-24
@react-router/dev - Update to Prettier v3 for formatting when running react-router reveal --no-typescript (#14049)⚠️ Unstable features are not recommended for production use
react-router - RSC Data Mode: fix bug where routes with errors weren't forced to revalidate when shouldRevalidate returned false (#14026)react-router - RSC Data Mode: fix Matched leaf route at location "/..." does not have an element or Component warnings when error boundaries are rendered (#14021)Full Changelog: v7.7.0...v7.7.1
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v770
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v770
Date: 2025-07-16
We're excited to introduce experimental support for RSC in Data Mode via the following new APIs:
unstable_RSCHydratedRouterunstable_RSCStaticRouterunstable_createCallServerunstable_getRSCStreamunstable_matchRSCServerRequestunstable_routeRSCServerRequestFor more information, check out the blog post and the RSC Docs.
create-react-router - Add Deno as a supported and detectable package manager. Note that this detection will only work with Deno versions 2.0.5 and above. If you are using an older version version of Deno then you must specify the --package-manager CLI flag set to deno. (#12327)@react-router/remix-config-routes-adapter - Export DefineRouteFunction type alongside DefineRoutesFunction (#13945)react-router - Handle InvalidCharacterError when validating cookie signature (#13847)
react-router - Pass a copy of searchParams to the setSearchParams callback function to avoid mutations of the internal searchParams instance (#12784)
searchParams when a navigation is blocked because the internal instance gets out of sync with useLocation().searchreact-router - Support invalid Date in turbo-stream v2 fork (#13684)
react-router - In Framework Mode, clear critical CSS in development after initial render (#13872, #13995)
react-router - Strip search parameters from patchRoutesOnNavigation path param for fetcher calls (#13911)
react-router - Skip scroll restoration on useRevalidator() calls because they're not new locations (#13671)
react-router - Support unencoded UTF-8 routes in prerender config with ssr set to false (#13699)
react-router - Do not throw if the url hash is not a valid URI component (#13247)
react-router - Remove Content-Length header from Single Fetch responses (#13902)
react-router - Fix a regression in createRoutesStub introduced with the middleware feature (#13946)
As part of that work we altered the signature to align with the new middleware APIs without making it backwards compatible with the prior AppLoadContext API
This permitted createRoutesStub to work if you were opting into middleware and the updated context typings, but broke createRoutesStub for users not yet opting into middleware
We've reverted this change and re-implemented it in such a way that both sets of users can leverage it
⚠️ This may be a breaking bug for if you have adopted the unstable Middleware feature and are using createRoutesStub with the updated API.
// If you have not opted into middleware, the old API should work again
let context: AppLoadContext = {
/*...*/
};
let Stub = createRoutesStub(routes, context);
// If you have opted into middleware, you should now pass an instantiated
// `unstable_routerContextProvider` instead of a `getContext` factory function.
let context = new unstable_RouterContextProvider();
context.set(SomeContext, someValue);
let Stub = createRoutesStub(routes, context);
@react-router/dev - Update vite-node to ^3.2.2 to support Vite 7 (#13781)
@react-router/dev - Properly handle https protocol in dev mode (#13746)
@react-router/dev - Fix missing styles when Vite's build.cssCodeSplit option is disabled (#13943)
@react-router/dev - Allow .mts and .mjs extensions for route config file (#13931)
@react-router/dev - Fix prerender file locations when cwd differs from project root (#13824)
@react-router/dev - Improve chunk error logging when a chunk cannot be found during the build (#13799)
@react-router/dev - Fix incorrectly configured externalConditions which had enabled module condition for externals and broke builds with certain packages (like Emotion) (#13871)
⚠️ Unstable features are not recommended for production use
create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.6.3...v7.7.0
Add unstable RSC support (#13700)
For more information, see the RSC documentation.
Handle InvalidCharacterError when validating cookie signature (#13847)
Pass a copy of searchParams to the setSearchParams callback function to avoid muations of the internal searchParams instance. This was an issue when navigations were blocked because the internal instance be out of sync with useLocation().search. (#12784)
Support invalid Date in turbo-stream v2 fork (#13684)
In Framework Mode, clear critical CSS in development after initial render (#13872)
Strip search parameters from patchRoutesOnNavigation path param for fetcher calls (#13911)
Skip scroll restoration on useRevalidator() calls because they're not new locations (#13671)
Support unencoded UTF-8 routes in prerender config with ssr set to false (#13699)
Do not throw if the url hash is not a valid URI component (#13247)
Fix a regression in createRoutesStub introduced with the middleware feature. (#13946)
As part of that work we altered the signature to align with the new middleware APIs without making it backwards compatible with the prior AppLoadContext API. This permitted createRoutesStub to work if you were opting into middleware and the updated context typings, but broke createRoutesStub for users not yet opting into middleware.
We've reverted this change and re-implemented it in such a way that both sets of users can leverage it.
// If you have not opted into middleware, the old API should work again
let context: AppLoadContext = {
/*...*/
};
let Stub = createRoutesStub(routes, context);
// If you have opted into middleware, you should now pass an instantiated `unstable_routerContextProvider` instead of a `getContext` factory function.
let context = new unstable_RouterContextProvider();
context.set(SomeContext, someValue);
let Stub = createRoutesStub(routes, context);
⚠️ This may be a breaking bug for if you have adopted the unstable Middleware feature and are using createRoutesStub with the updated API.
Remove Content-Length header from Single Fetch responses (#13902)
Nothing published for this version
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v763
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v763
Date: 2025-06-27
react-router - Do not serialize types for useRouteLoaderData<typeof clientLoader> (#13752)
For types to distinguish a clientLoader from a serverLoader, you MUST annotate clientLoader args:
// 👇 annotation required to skip serializing types
export function clientLoader({}: Route.ClientLoaderArgs) {
return { fn: () => "earth" };
}
function SomeComponent() {
const data = useRouteLoaderData<typeof clientLoader>("routes/this-route");
const planet = data?.fn() ?? "world";
return <h1>Hello, {planet}!</h1>;
}
@react-router/cloudflare - Remove tsup from peerDependencies (#13757)
@react-router/dev - Add Vite 7 support (#13748)
@react-router/dev - Skip package.json resolution checks when a custom entry.server.(j|t)sx file is provided (#13744)
@react-router/dev - Add validation for a route's id not being 'root' (#13792)
@react-router/fs-routes @react-router/remix-config-routes-adapter - Use replaceAll for normalizing windows file system slashes (#13738)
@react-router/node - Remove old "install" package exports (#13762)
Full Changelog: v7.6.2...v7.6.3
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v762
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v762
Date: 2025-06-03
create-react-router - Update tar-fs (#13675)
react-router - (INTERNAL) Slight refactor of internal headers() function processing for use with RSC (#13639)
react-router @react-router/dev - Avoid additional with-props chunk in Framework Mode by moving route module component prop logic from the Vite plugin to react-router (#13650)
@react-router/dev - When future.unstable_viteEnvironmentApi is enabled and an absolute Vite base has been configured, ensure critical CSS is handled correctly during development (#13598)
@react-router/dev - Update vite-node (#13673)
@react-router/dev - Fix typegen for non-{.js,.jsx,.ts,.tsx} routes like .mdx (#12453)
@react-router/dev - Fix href types for optional dynamic params (#13725)
7.6.1 introduced fixes for href when using optional static segments,
but those fixes caused regressions with how optional dynamic params worked in 7.6.0:
// 7.6.0
href("/users/:id?"); // ✅
href("/users/:id?", { id: 1 }); // ✅
// 7.6.1
href("/users/:id?"); // ❌
href("/users/:id?", { id: 1 }); // ❌
Now, optional static segments are expanded into different paths for href, but optional dynamic params are not.
This way href can unambiguously refer to an exact URL path, all while keeping the number of path options to a minimum.
// 7.6.2
// path: /users/:id?/edit?
href("
// ^ suggestions when cursor is here:
//
// /users/:id?
// /users/:id?/edit
Additionally, you can pass params from component props without needing to narrow them manually:
declare const params: { id?: number };
// 7.6.0
href("/users/:id?", params);
// 7.6.1
href("/users/:id?", params); // ❌
"id" in params ? href("/users/:id", params) : href("/users"); // works... but is annoying
// 7.6.2
href("/users/:id?", params); // restores behavior of 7.6.0
Full Changelog: v7.6.1...v7.6.2
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v761
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v761
Date: 2025-05-25
react-router - Partially revert optimization added in 7.1.4 to reduce calls to matchRoutes because it surfaced other issues (#13562)
react-router - Update Route.MetaArgs to reflect that data can be potentially undefined (#13563)
loader threw an error to it's own ErrorBoundary, but it also arises in the case of a 404 which renders the root ErrorBoundary/meta but the root loader did not run because not routes matchedreact-router - Avoid initial fetcher execution 404 error when Lazy Route Discovery is interrupted by a navigation (#13564)
react-router - Properly href replaces splats * (#13593)
href("/products/*", { "*": "/1/edit" }); // -> /products/1/edit@react-router/architect - Update @architect/functions from ^5.2.0 to ^7.0.0 (#13556)
@react-router/dev - Prevent typegen with route files that are outside the app/ directory (#12996)
@react-router/dev - Add additional logging to build command output when cleaning assets from server build (#13547)
@react-router/dev - Don't clean assets from server build when build.ssrEmitAssets has been enabled in Vite config (#13547)
@react-router/dev - Fix typegen when same route is used at multiple paths (#13574)
For example, routes/route.tsx is used at 4 different paths here:
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route("base/:base", "routes/base.tsx", [
route("home/:home", "routes/route.tsx", { id: "home" }),
route("changelog/:changelog", "routes/route.tsx", { id: "changelog" }),
route("splat/*", "routes/route.tsx", { id: "splat" }),
]),
route("other/:other", "routes/route.tsx", { id: "other" }),
] satisfies RouteConfig;
Previously, typegen would arbitrarily pick one of these paths to be the "winner" and generate types for the route module based on that path
Now, typegen creates unions as necessary for alternate paths for the same route file
@react-router/dev - Better types for params (#13543)
For example:
// routes.ts
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route("parent/:p", "routes/parent.tsx", [
route("route/:r", "routes/route.tsx", [
route("child1/:c1a/:c1b", "routes/child1.tsx"),
route("child2/:c2a/:c2b", "routes/child2.tsx"),
]),
]),
] satisfies RouteConfig;
Previously, params for routes/route were calculated as { p: string, r: string }.
This incorrectly ignores params that could come from child routes
If visiting /parent/1/route/2/child1/3/4, the actual params passed to routes/route will have a type of { p: string, r: string, c1a: string, c1b: string }
Now, params are aware of child routes and autocompletion will include child params as optionals:
params.|
// ^ cursor is here and you ask for autocompletion
// p: string
// r: string
// c1a?: string
// c1b?: string
// c2a?: string
// c2b?: string
You can also narrow the types for params as it is implemented as a normalized union of params for each page that includes routes/route:
if (typeof params.c1a === 'string') {
params.|
// ^ cursor is here and you ask for autocompletion
// p: string
// r: string
// c1a: string
// c1b: string
}
@react-router/dev - Fix href for optional segments (#13595)
Type generation now expands paths with optionals into their corresponding non-optional paths
For example, the path /user/:id? gets expanded into /user and /user/:id to more closely model visitable URLs
href then uses these expanded (non-optional) paths to construct type-safe paths for your app:
// original: /user/:id?
// expanded: /user & /user/:id
href("/user"); // ✅
href("/user/:id", { id: 1 }); // ✅
This becomes even more important for static optional paths where there wasn't a good way to indicate whether the optional should be included in the resulting path:
// original: /products/:id/detail?
// before
href("/products/:id/detail?"); // ❌ How can we tell `href` to include or omit `detail?` segment with a complex API?
// now
// expanded: /products/:id & /products/:id/detail
href("/product/:id"); // ✅
href("/product/:id/detail"); // ✅
⚠️ Unstable features are not recommended for production use
@react-router/dev - Renamed internal react-router/route-module export to react-router/internal (#13543)@react-router/dev - Removed Info export from generated +types/* files (#13543)@react-router/dev - Normalize dirent entry path across node versions when generating SRI manifest (#13591)Full Changelog: v7.6.0...v7.6.1
Update Route.MetaArgs to reflect that data can be potentially undefined (#13563)
This is primarily for cases where a route loader threw an error to it's own ErrorBoundary. but it also arises in the case of a 404 which renders the root ErrorBoundary/meta but the root loader did not run because not routes matched.
Partially revert optimization added in 7.1.4 to reduce calls to matchRoutes because it surfaced other issues (#13562)
Fix typegen when same route is used at multiple paths (#13574)
For example, routes/route.tsx is used at 4 different paths here:
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route("base/:base", "routes/base.tsx", [
route("home/:home", "routes/route.tsx", { id: "home" }),
route("changelog/:changelog", "routes/route.tsx", { id: "changelog" }),
route("splat/*", "routes/route.tsx", { id: "splat" }),
]),
route("other/:other", "routes/route.tsx", { id: "other" }),
] satisfies RouteConfig;
Previously, typegen would arbitrarily pick one of these paths to be the "winner" and generate types for the route module based on that path. Now, typegen creates unions as necessary for alternate paths for the same route file.
Better types for params (#13543)
For example:
// routes.ts
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route("parent/:p", "routes/parent.tsx", [
route("layout/:l", "routes/layout.tsx", [
route("child1/:c1a/:c1b", "routes/child1.tsx"),
route("child2/:c2a/:c2b", "routes/child2.tsx"),
]),
]),
] satisfies RouteConfig;
Previously, params for the routes/layout.tsx route were calculated as { p: string, l: string }.
This incorrectly ignores params that could come from child routes.
If visiting /parent/1/layout/2/child1/3/4, the actual params passed to routes/layout.tsx will have a type of { p: string, l: string, c1a: string, c1b: string }.
Now, params are aware of child routes and autocompletion will include child params as optionals:
params.|
// ^ cursor is here and you ask for autocompletion
// p: string
// l: string
// c1a?: string
// c1b?: string
// c2a?: string
// c2b?: string
You can also narrow the types for params as it is implemented as a normalized union of params for each page that includes routes/layout.tsx:
if (typeof params.c1a === 'string') {
params.|
// ^ cursor is here and you ask for autocompletion
// p: string
// l: string
// c1a: string
// c1b: string
}
UNSTABLE: renamed internal react-router/route-module export to react-router/internal
UNSTABLE: removed Info export from generated +types/* files
Avoid initial fetcher execution 404 error when Lazy Route Discovery is interrupted by a navigation (#13564)
href replaces splats * (#13593)
const a = href("/products/*", { "*": "/1/edit" });
// -> /products/1/edit
Nothing published for this version
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v760
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v760
Date: 2025-05-08
routeDiscovery Config OptionWe've added a new config option in 7.6.0 which grants you more control over the Lazy Route Discovery feature. You can now configure the /__manifest path if you're running multiple RR applications on the same server, or you can also disable the feature entirely if your application is small enough and the feature isn't necessary.
// react-router.config.ts
export default {
// You can modify the manifest path used:
routeDiscovery: { mode: "lazy", manifestPath: "/custom-manifest" }
// Or you can disable this feature entirely and include all routes in the
// manifest on initial document load:
routeDiscovery: { mode: "initial" }
// If you don't specify anything, the default config is as follows, which enables
// Lazy Route Discovery and makes manifest requests to the `/__manifest` path:
// routeDiscovery: { mode: "lazy", manifestPath: "/__manifest" }
} satisfies Config;
Some future flags alter the way types should work in React Router. Previously, you had to remember to manually opt-in to the new types. For example, for future.unstable_middleware:
// react-router.config.ts
// Step 1: Enable middleware
export default {
future: {
unstable_middleware: true,
},
};
// Step 2: Enable middleware types
declare module "react-router" {
interface Future {
unstable_middleware: true; // 👈 Enable middleware types
}
}
It was up to you to keep the runtime future flags synced with the types for those flags. This was confusing and error-prone.
Now, React Router will automatically enable types for future flags. That means you only need to specify the runtime future flag:
// react-router.config.ts
// Step 1: Enable middleware
export default {
future: {
unstable_middleware: true,
},
};
// No step 2! That's it!
Behind the scenes, React Router will generate the corresponding declare module into .react-router/types. Currently this is done in .react-router/types/+register.ts but this is an implementation detail that may change in the future.
react-router - Added a new routeDiscovery option in react-router.config.ts to configure Lazy Route Discovery behavior (#13451)
react-router - Add support for route component props in createRoutesStub (#13528)
This allows you to unit test your route components using the props instead of the hooks:
let RoutesStub = createRoutesStub([
{
path: "/",
Component({ loaderData }) {
let data = loaderData as { message: string };
return <pre data-testid="data">Message: {data.message}</pre>;
},
loader() {
return { message: "hello" };
},
},
]);
render(<RoutesStub />);
await waitFor(() => screen.findByText("Message: hello"));
@react-router/dev - Automatic types for future flags (#13506)
You may notice this list is a bit larger than usual! The team ate their vegetables last week and spent the week squashing bugs to work on lowering the issue count that had ballooned a bit since the v7 release.
react-router - Fix react-router module augmentation for NodeNext (#13498)react-router - Don't bundle react-router in react-router/dom CJS export (#13497)react-router - Fix bug where a submitting fetcher would get stuck in a loading state if a revalidating loader redirected (#12873)react-router - Fix hydration error if a server loader returned undefined (#13496)react-router - Fix initial load 404 scenarios in data mode (#13500)react-router - Stabilize useRevalidator's revalidate function (#13542)react-router - Preserve status code if a clientAction throws a data() result in framework mode (#13522)react-router - Be defensive against leading double slashes in paths to avoid Invalid URL errors from the URL constructor (#13510)
new URL("//", window.location.origin)react-router - Remove Navigator declaration for navigator.connection.saveData to avoid messing with any other types beyond saveData in user land (#13512)react-router - Fix handleError params values on .data requests for routes with a dynamic param as the last URL segment (#13481)react-router - Don't trigger an ErrorBoundary UI before the reload when we detect a manifest version mismatch in Lazy Route Discovery (#13480)react-router - Inline turbo-stream@2.4.1 dependency and fix decoding ordering of Map/Set instances (#13518)react-router - Only render dev warnings during dev (#13461)react-router - Short circuit post-processing on aborted dataStrategy requests (#13521)
Cannot read properties of undefined (reading 'result')@react-router/dev - Support project root directories without a package.json if it exists in a parent directory (#13472)@react-router/dev - When providing a custom Vite config path via the CLI --config/-c flag, default the project root directory to the directory containing the Vite config when not explicitly provided (#13472)@react-router/dev - In a routes.ts context, ensure the --mode flag is respected for import.meta.env.MODE (#13485)
import.meta.env.MODE within a routes.ts context was always "development" for the dev and typegen --watch commands, but otherwise resolved to "production". These defaults are still in place, but if a --mode flag is provided, this will now take precedence.@react-router/dev - Ensure consistent project root directory resolution logic in CLI commands (#13472)@react-router/dev - When executing react-router.config.ts and routes.ts with vite-node, ensure that PostCSS config files are ignored (#13489)@react-router/dev - When extracting critical CSS during development, ensure it's loaded from the client environment to avoid issues with plugins that handle the SSR environment differently (#13503)@react-router/dev - Fix "Status message is not supported by HTTP/2" error during dev when using HTTPS (#13460)@react-router/dev - Update config when react-router.config.ts is created or deleted during development (#12319)@react-router/dev - Skip unnecessary routes.ts evaluation before Vite build is started (#13513)@react-router/dev - Fix TS2300: Duplicate identifier errors caused by generated types (#13499)href (.react-router/types/+register.ts), causing type checking errors⚠️ Unstable features are not recommended for production use
react-router - Fix a few bugs with error bubbling in middleware use-cases (#13538)@react-router/dev - When future.unstable_viteEnvironmentApi is enabled, ensure that build.assetsDir in Vite config is respected when environments.client.build.assetsDir is not configured (#13491)create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.5.3...v7.6.0
Added a new react-router.config.ts routeDiscovery option to configure Lazy Route Discovery behavior. (#13451)
/__manifest path:
routeDiscovery: { mode: "lazy", manifestPath: "/__manifest" }routeDiscovery: { mode: "lazy", manifestPath: "/custom-manifest" }routeDiscovery: { mode: "initial" }Add support for route component props in createRoutesStub. This allows you to unit test your route components using the props instead of the hooks: (#13528)
let RoutesStub = createRoutesStub([
{
path: "/",
Component({ loaderData }) {
let data = loaderData as { message: string };
return <pre data-testid="data">Message: {data.message}</pre>;
},
loader() {
return { message: "hello" };
},
},
]);
render(<RoutesStub />);
await waitFor(() => screen.findByText("Message: hello"));
Fix react-router module augmentation for NodeNext (#13498)
Don't bundle react-router in react-router/dom CJS export (#13497)
Fix bug where a submitting fetcher would get stuck in a loading state if a revalidating loader redirected (#12873)
Fix hydration error if a server loader returned undefined (#13496)
Fix initial load 404 scenarios in data mode (#13500)
Stabilize useRevalidator's revalidate function (#13542)
Preserve status code if a clientAction throws a data() result in framework mode (#13522)
Be defensive against leading double slashes in paths to avoid Invalid URL errors from the URL constructor (#13510)
new URL("//", window.location.origin)Remove Navigator declaration for navigator.connection.saveData to avoid messing with any other types beyond saveData in userland (#13512)
Fix handleError params values on .data requests for routes with a dynamic param as the last URL segment (#13481)
Don't trigger an ErrorBoundary UI before the reload when we detect a manifest verison mismatch in Lazy Route Discovery (#13480)
Inline turbo-stream@2.4.1 dependency and fix decoding ordering of Map/Set instances (#13518)
Only render dev warnings in DEV mode (#13461)
UNSTABLE: Fix a few bugs with error bubbling in middleware use-cases (#13538)
Short circuit post-processing on aborted dataStrategy requests (#13521)
Cannot read properties of undefined (reading 'result')Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v753
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v753
Date: 2025-04-28
react-router - Fix bug where bubbled action errors would result in loaderData being cleared at the handling ErrorBoundary route (#13476)react-router - Handle redirects from clientLoader.hydrate initial load executions (#13477)Full Changelog: v7.5.2...v7.5.3
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v752
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v752
Date: 2025-04-24
Fixed 2 security vulnerabilities that could result in cache-poisoning attacks by sending specific headers intended for build-time usage for SPA Mode and Pre-rendering (GHSA-f46r-rw29-r322, GHSA-cpj6-fhp6-mr6j).
react-router - Adjust approach for Pre-rendering/SPA Mode via headers (#13453)react-router - Update Single Fetch to also handle the 204 redirects used in ?_data requests in Remix v2 (#13364)
.data requests from outside the scope of React Router (i.e., an express/hono middleware) the same way they did in Remix v2 before Single Fetch was implemented.data request wih a response as follows:
X-Remix-Redirect: <new-location> headerX-Remix-Replace: true or X-Remix-Reload-Document: true headers to replicate replace()/redirectDocument() functionalityFull Changelog: v7.5.1...v7.5.2
Update Single Fetch to also handle the 204 redirects used in ?_data requests in Remix v2 (#13364)
.data requests from outside the scope of React Router (i.e., an express/hono middleware)Adjust approach for Prerendering/SPA Mode via headers (#13453)
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v751
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v751
Date: 2025-04-17
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)
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 checkcreateStaticHandler()/<StaticRouterProvider>, and using context.loaderData to control <RouterProvider> hydration behavior on the client⚠️ 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
Fix single fetch bug where no revalidation request would be made when navigating upwards to a reused parent route (#13253)
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, you can use this optimization to avoid downloading unused hydration code. 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,
},
},
]);
Properly revalidate prerendered paths when param values change (#13380)
UNSTABLE: Add a new unstable_runClientMiddleware argument to dataStrategy to enable middleware execution in custom dataStrategy implementations (#13395)
UNSTABLE: Add better error messaging when getLoadContext is not updated to return a Map" (#13242)
Do not automatically add null to staticHandler.query() context.loaderData if routes do not have loaders (#13223)
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 checkcreateStaticHandler()/<StaticRouterProvider>, and using context.loaderData to control <RouterProvider> hydration behavior on the clientFix prerendering when a loader returns a redirect (#13365)
UNSTABLE: Update context type for LoaderFunctionArgs/ActionFunctionArgs when middleware is enabled (#13381)
Add support for the new unstable_shouldCallHandler/unstable_shouldRevalidateArgs APIs in dataStrategy (#13253)
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v750
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v750
Date: 2025-04-04
route.lazy Object APIWe'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.
react-router - Add granular object-based API for route.lazy to support lazy loading of individual route properties (#13294)@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 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)
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)create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.4.1...v7.5.0
Add granular object-based API for route.lazy to support lazy loading of individual route properties, for example: (#13294)
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,
},
},
]);
Breaking change for route.unstable_lazyMiddleware consumers
The route.unstable_lazyMiddleware property is no longer supported. If you want to lazily load middleware, you must use the new object-based route.lazy API with route.lazy.unstable_middleware, for example:
createBrowserRouter([
{
path: "/show/:showId",
lazy: {
unstable_middleware: async () =>
(await import("./show.middleware.js")).middleware,
// etc.
},
},
]);
unstable_subResourceIntegrity future flag that enables generation of an importmap with integrity for the scripts that will be loaded by the browser. (#13163)Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v741
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v741
Date: 2025-03-28
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 (GHSA-4q56-crqp-v477/CVE-2025-31137).
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 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)
unstable_middleware from route.lazyroute.unstable_middleware property is no longer supported in the return value from route.lazyroute.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
Fix types on unstable_MiddlewareFunction to avoid type errors when a middleware doesn't return a value (#13311)
Dedupe calls to route.lazy functions (#13260)
Add support for route.unstable_lazyMiddleware function to allow lazy loading of middleware logic. (#13210)
Breaking change for unstable_middleware consumers
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.
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v740
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v740
Date: 2025-03-19
@react-router/dev - Generate types for virtual:react-router/server-build module (#13152)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 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)
catch-ing errors thrown by the next() function in your middlewaresreact-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)create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.3.0...v7.4.0
shouldRevalidate behavior for clientLoader-only routes in ssr:true apps (#13221)RequestHandler loadContext parameter type when middleware is enabled (#13204)Route.unstable_MiddlewareFunction to have a return value of Response | undefined instead of Response | void becaue you should not return anything if you aren't returning the Response (#13199)next() and are no longer leaking the MiddlewareError implementation detail (#13180)Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v730
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v730
Date: 2025-03-06
fetcherKey as a parameter to patchRoutesOnNavigation (#13061)react-router - Detect and handle manifest-skew issues on new deploys during active sessions (#13061)
fetcher calls to undiscovered routes, this mismatch will trigger a document reload of the current pathreact-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)
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)
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 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)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:
createBrowserRouter(routes, { unstable_getContext })<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 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 "@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.
context parameterWhen 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_SerializesTounstable_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.
create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.2.0...v7.3.0
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v720
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v720
Date: 2025-02-18
href utilityIn 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!
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.
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 pathssr: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 applicationssr: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 pathsFor more info, see the Pre-rendering docs for more info.
loader in SPA ModeSPA 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:
HydrateFallback is rendering because children routes are loadingundefined if the HydrateFallback is rendering because the route itself has it's own hydrating clientLoader
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 loaderDatareact-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)
ssr:false appsprerender scenarios we would pre-render the /__manifest file but that makes some unnecessary assumptions about the static file server behaviorsreact-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)
.data requests if the path wasn't pre-rendered because the request will 404loader data in ssr:false mode is static because it's generated at build timeclientLoader to do anything dynamicloader and not a clientLoader, we disable revalidation by default because there is no new data to retrieve.data request logic if there are no server loaders with shouldLoad=true in our single fetch dataStrategy.data request that would 404 after a submissionreact-router - Align dev server behavior with static file server behavior when ssr:false is set (#12948)
prerender config exists, only SSR down to the root HydrateFallback (SPA Mode)prerender config exists but the current path is not pre-rendered, only SSR down to the root HydrateFallback (SPA Fallback).data requests to non-pre-rendered pathsreact-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 themloader functions are more nuanced and depend on whether a given route is prerendered
ssr:false without a prerender config, only the root route can have a loaderssr: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)
loader (does not have a clientLoader)loaderData because there is no server on which to run the loaderclientLoader or pre-rendering the child pathsclientLoader, 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)
/a/:id/b/:id?/c/:id, the last :id will set the value for id in useParams and the params prop
/a/1/b/2/c/3 will result in the value { id: 3 } at runtime/a/1/b/2/c/3 generated a type like { id: [1,2,3] }./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 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)⚠️ This feature is currently unstable, enabled by the
future.unstable_splitRouteModulesflag. 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:
<docs-info>In the following timeline diagrams, different characters are used within the Route Module bars to denote the different Route Module APIs being exported.</docs-info>
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.lazyand 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.
create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.1.5...v7.2.0
New type-safe href utility that guarantees links point to actual paths in your app (#13012)
import { href } from "react-router";
export default function Component() {
const link = href("/blog/:slug", { slug: "my-first-post" });
return (
<main>
<Link to={href("/products/:id", { id: "asdf" })} />
<NavLink to={href("/:lang?/about", { lang: "en" })} />
</main>
);
}
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.
So /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.
So /a/1/b/2/c/3 now generates a type like { id: 3 }.
Don't apply Single Fetch revalidation de-optimization when in SPA mode since there is no server HTTP request (#12948)
Properly handle revalidations to across a prerender/SPA boundary (#13021)
.data requests if the path wasn't pre-rendered because the request will 404loader data in ssr:false mode is static because it's generated at build timeclientLoader to do anything dynamicloader and not a clientLoader, we disable revalidation by default because there is no new data to retrieve.data request logic if there are no server loaders with shouldLoad=true in our single fetch dataStrategy.data request that would 404 after a submissionError at build time in ssr:false + prerender apps for the edge case scenario of: (#13021)
loader (does not have a clientLoader)loaderData because there is no server on which to run the loaderclientLoader or pre-rendering the child pathsclientLoader, calling the serverLoader() on non-prerendered paths will throw a 404Add unstable support for splitting route modules in framework mode via future.unstable_splitRouteModules (#11871)
Add unstable_SerializesTo brand type for library authors to register types serializable by React Router's streaming format (turbo-stream) (ab5b05b02)
Align dev server behavior with static file server behavior when ssr:false is set (#12948)
prerender config exists, only SSR down to the root HydrateFallback (SPA Mode)prerender config exists but the current path is not prerendered, only SSR down to the root HydrateFallback (SPA Fallback).data requests to non-pre-rendered pathsImprove prefetch performance of CSS side effects in framework mode (#12889)
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)
ssr:false and no prerender config but we realized it should apply to all ssr:false apps, including those prerendering multiple pagesprerender scenarios we would prerender the /__manifest file assuming the static file server would serve it but that makes some unneccesary assumptions about the static file server behaviorsProperly handle interrupted manifest requests in lazy route discovery (#12915)
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v715
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v715
Date: 2025-01-31
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
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v714
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v714
Date: 2025-01-30
@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)
Set-Cookie headers if also returned from headersreact-router - Stop erroring on resource routes that return raw strings/objects and instead serialize them as text/plain or application/json responses (#12848)
.data extension.data request, they will still be encoded via turbo-streamreact-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
text/plain or application/json responses (#12848)
.data extension.data request, they will still be encoded via turbo-streamquerySelectorAll call at the body level instead of many calls at the sub-tree level (#12731)errorHeaders when throwing a data() result (#12846)
Set-Cookie headers could be duplicated if also returned from headersmatchRoutes calls when possible (#12800)Nothing published for this version
@react-router/dev - Fix reveal and routes CLI commands
Date: 2025-01-17
@react-router/dev - Fix reveal and routes CLI commands (#12745)Full Changelog: v7.1.2...v7.1.3
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v712
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v712
Date: 2025-01-16
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)
error TS4058: Return type of exported function has or is using name 'redirectSymbol' from external module "node_modules/..." but cannot be named.
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)
@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
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v711
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v711
Date: 2024-12-23
@react-router/dev - Fix for a crash when optional args are passed to the CLI (#12609)Full Changelog: v7.1.0...v7.1.1
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v710
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v710
Date: 2024-12-20
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)
abortDelay functionality was removed in v7 as it was coupled to the defer implementation from Remix v2, but this removal of this prop was missedentry.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)create-react-routerreact-router@react-router/architect@react-router/cloudflare@react-router/dev@react-router/express@react-router/fs-routes@react-router/node@react-router/remix-config-routes-adapter@react-router/serveFull Changelog: v7.0.2...v7.1.0
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v702
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v702
Date: 2024-12-02
react-router - Temporarily only use one build in export map so packages can have a peer dependency on react router (#12437)@react-router/dev - Support moduleResolution Node16 and NodeNext (#12440)@react-router/dev - Generate wide matches and params types for child routes (#12397)
matches includes child route matches and params include child route path parametersmatches and paramsFull Changelog: v7.0.1...v7.0.2
temporarily only use one build in export map so packages can have a peer dependency on react router (#12437)
Generate wide matches and params types for current route and child routes (#12397)
At runtime, matches includes child route matches and params include child route path parameters.
But previously, we only generated types for parent routes in matches; for params, we only considered the parent routes and the current route.
To align our generated types more closely to the runtime behavior, we now generate more permissive, wider types when accessing child route information.
Nothing published for this version
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v701
See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v701
Date: 2024-11-22
@react-router/dev - Ensure typegen file watcher is cleaned up when Vite dev server restarts (#12331)@react-router/dev - Pass route error to ErrorBoundary as a prop (#12338)Full Changelog: v7.0.0...v7.0.1
Your coding agent can read these notes before it upgrades. Set up the MCP server →