UNPKG

14.3 kBMarkdownView Raw
1---
2title: Updating from v7
3order: 2
4---
5
6# Upgrading from v7
7
8We try our best to keep major version upgrades simple and boring through the use of opt-in APIs and [Future Flags][api-development-strategy]. Future flags are used to gate breaking changes that don't otherwise have a good call-site opt-in strategy. By adopting all opt-in APIs and future flags while still on v7, you should be able to update to React Router v8 with minimal changes.
9
10We highly recommend you make a commit after each step and ship it instead of doing everything all at once. Most flags can be adopted in any order, with exceptions noted below.
11
12## Minimum Versions
13
14[MODES: framework, data, declarative]
15
16<br/>
17<br/>
18
19React Router v8 requires the following minimum versions. Update them before updating React Router to v8:
20
21- `node@22.22+`
22- `react@19.2.7+`/`react-dom@19.2.7+`
23
24Framework mode also requires:
25
26- `vite@7+` (requires `future.v8_viteEnvironmentApi`)
27 - also make sure any custom Vite plugins or config are compatible with Vite 7
28
29## Update to latest v7.x
30
31Before adopting any future flags or call-site opt-in changes, update to the latest minor version of v7.x to make sure you have access to the latest flags. You may see a number of deprecation warnings as you upgrade, which we'll cover below.
32
33👉 Update to latest v7
34
35```sh
36npm install react-router@7 @react-router/{dev,node,etc.}@7
37```
38
39## Future Flags
40
41### `future.v8_middleware`
42
43[MODES: framework, data]
44
45<br/>
46<br/>
47
48**Background**
49
50Middleware allows you to run code before and after the [`Response`][Response] generation for the matched path. This enables common patterns like authentication, logging, error handling, and data preprocessing in a reusable way. Please see the [docs](../how-to/middleware) for more information.
51
52👉 **Enable the Flag**
53
54In Framework mode:
55
56```ts filename=react-router.config.ts
57import type { Config } from "@react-router/dev/config";
58
59export default {
60 future: {
61 v8_middleware: true,
62 },
63} satisfies Config;
64```
65
66In Data mode:
67
68```ts
69import { createBrowserRouter } from "react-router";
70
71const router = createBrowserRouter(routes, {
72 future: {
73 v8_middleware: true,
74 },
75});
76```
77
78**Update your Code**
79
80If you're using the `context` parameter in `loader` and `action` functions, you may need to update your code:
81
82- In Framework mode, if you're using `react-router-serve`, you should not need to make any updates. Otherwise, this only applies if you have a custom server with a `getLoadContext` function. Please see the docs on the middleware [`getLoadContext` changes](../how-to/middleware#changes-to-getloadcontextapploadcontext) and the instructions to [migrate to the new API](../how-to/middleware#migration-from-apploadcontext).
83- In Data mode, add the `Future` module augmentation described in the [middleware docs](../how-to/middleware#1-typescript-augment-future-for-loaderaction-context) so `context` is typed correctly.
84
85### `future.v8_splitRouteModules`
86
87[MODES: framework]
88
89<br/>
90<br/>
91
92**Background**
93
94This feature enables splitting client-side route exports (`clientLoader`, `clientAction`, `clientMiddleware`, `HydrateFallback`) into separate chunks that can be loaded independently from the route component. This allows these exports to be fetched and executed while the component code is still downloading, improving performance for client-side data loading.
95
96This can be set to `true` for opt-in behavior, or `"enforce"` to require all routes to be splittable (which will cause build failures for routes that cannot be split due to shared code).
97
98👉 **Enable the Flag**
99
100```ts filename=react-router.config.ts
101import type { Config } from "@react-router/dev/config";
102
103export default {
104 future: {
105 v8_splitRouteModules: true,
106 },
107} satisfies Config;
108```
109
110**Update your Code**
111
112No code changes are required. This is an optimization feature that works automatically once enabled.
113
114### `future.v8_viteEnvironmentApi`
115
116[MODES: framework]
117
118<br/>
119<br/>
120
121**Background**
122
123This enables support for the experimental Vite Environment API, which provides a more flexible and powerful way to configure Vite environments. This is only available when using Vite 6+.
124
125👉 **Enable the Flag**
126
127```ts filename=react-router.config.ts
128import type { Config } from "@react-router/dev/config";
129
130export default {
131 future: {
132 v8_viteEnvironmentApi: true,
133 },
134} satisfies Config;
135```
136
137**Update your Code**
138
139Most users won't need to make any changes. However, if you have custom Vite configuration that previously relied on the `isSsrBuild` flag — such as a custom server build that sets `build.rollupOptions.input` — you'll need to move that configuration under the per-environment [Environment API][vite-environment] config instead.
140
141For example, a custom server build should move its SSR `rollupOptions` from the top-level `build` config into `environments.ssr.build`:
142
143```tsx diff filename=vite.config.ts
144import { reactRouter } from "@react-router/dev/vite";
145import { defineConfig } from "vite";
146
147-export default defineConfig(({ isSsrBuild }) => ({
148- build: {
149- rollupOptions: isSsrBuild
150- ? {
151- input: "./server/app.ts",
152- }
153- : undefined,
154- },
155+export default defineConfig({
156+ environments: {
157+ ssr: {
158+ build: {
159+ rollupOptions: {
160+ input: "./server/app.ts",
161+ },
162+ },
163+ },
164+ },
165 plugins: [reactRouter()],
166-}));
167+});
168```
169
170See the [`node-custom-server` template][node-custom-server-template] for a complete example.
171
172### `future.v8_passThroughRequests`
173
174[MODES: framework]
175
176<br/>
177<br/>
178
179**Background**
180
181By default, React Router normalizes the `request.url` passed to your `loader`, `action`, and `middleware` functions by removing React Router's internal implementation details. Specifically, it removes `.data` suffixes and internal search parameters like `?index` and `?_routes`.
182
183This flag eliminates that normalization and passes the raw HTTP `request` instance to your handlers. This provides a few benefits:
184
185- Reduces server-side overhead by eliminating multiple `new Request()` calls on the critical path
186- Allows you to distinguish document from data requests in your handlers based on the presence of a `.data` suffix (useful for [observability] purposes)
187
188If you were previously relying on the normalization of `request.url`, you can switch to use the new sibling `url` parameter which contains a `URL` instance representing the normalized location.
189
190👉 **Enable the Flag**
191
192```ts filename=react-router.config.ts
193import type { Config } from "@react-router/dev/config";
194
195export default {
196 future: {
197 v8_passThroughRequests: true,
198 },
199} satisfies Config;
200```
201
202**Update your Code**
203
204If your code relies on inspecting the request URL, you should review it for any assumptions about the URL format:
205
206```tsx
207// ❌ Before: assuming no `.data` suffix in `request.url` pathname
208export async function loader({
209 request,
210}: Route.LoaderArgs) {
211 let url = new URL(request.url);
212 if (url.pathname === "/path") {
213 // This check might now behave differently because the request pathname will
214 // contain the `.data` suffix on data requests
215 }
216}
217
218// ✅ After: use `url` for normalized routing logic and `request.url`
219// for raw routing logic
220export async function loader({
221 request,
222 url,
223}: Route.LoaderArgs) {
224 if (url.pathname === "/path") {
225 // This will always have the `.data` suffix stripped
226 }
227
228 // And now you can distinguish between document versus data requests
229 let isDataRequest = new URL(
230 request.url,
231 ).pathname.endsWith(".data");
232}
233```
234
235### `future.v8_trailingSlashAwareDataRequests`
236
237[MODES: framework]
238
239<br/>
240<br/>
241
242**Background**
243
244React Router serves Framework mode data requests from `.data` URLs. Previously, data requests for routes with and without trailing slashes could map to the same `.data` URL because trailing slashes were not considered during URL generation. This flag preserves trailing slash semantics for data request URLs to avoid ambiguity when your app distinguishes between trailing-slash and non-trailing-slash URLs.
245
246In v7, your HTTP and `request` pathnames are as follows for `/a/b/c` and `/a/b/c/`
247
248| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
249| ------------ | ----------------- | ----------------------- |
250| **Document** | `/a/b/c` | `/a/b/c` ✅ |
251| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
252
253| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
254| ------------- | ----------------- | ----------------------- |
255| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
256| **Data** | `/a/b/c.data` | `/a/b/c` ⚠️ |
257
258With this flag enabled, these pathnames are made consistent through a new `_.data` format for client-side `.data` requests:
259
260| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
261| ------------ | ----------------- | ----------------------- |
262| **Document** | `/a/b/c` | `/a/b/c` ✅ |
263| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
264
265| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
266| ------------- | ------------------ | ----------------------- |
267| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
268| **Data** | `/a/b/c/_.data` ⬅️ | `/a/b/c/` ✅ |
269
270This flag also aligns the root data request to match this behavior by changing it from `/_root.data` to `/_.data`.
271
272👉 **Enable the Flag**
273
274```ts filename=react-router.config.ts
275import type { Config } from "@react-router/dev/config";
276
277export default {
278 future: {
279 v8_trailingSlashAwareDataRequests: true,
280 },
281} satisfies Config;
282```
283
284**Update your Code**
285
286If you have custom app, CDN, cache, or rewrite logic that matches `.data` request URLs, update it to handle the new trailing-slash-aware `/_.data` format.
287
288## Other Breaking Changes
289
290The changes in this section are not controlled by future flags, but you can update your code while still on v7 before moving to v8.
291
292### `meta`/`matches` `data` Values
293
294[MODES: framework]
295
296<br/>
297<br/>
298
299**Background**
300
301React Router v8 removed deprecated `data` fields in favor of `loaderData` in a few places:
302
303- `meta` function `data` argument
304- `meta` function `matches` argument (`matches[i].data`)
305- `useMatches()` (`matches[i].data`)
306
307Use `loaderData` instead on `MetaArgs`, each item in `MetaArgs.matches`, and each match returned from `useMatches()`
308
309👉 **Update your Code**
310
311Replace `data` with `loaderData` in your `meta` functions:
312
313```tsx diff
314export function meta({
315- data,
316+ loaderData,
317 matches,
318}: Route.MetaArgs) {
319 return [
320 {
321- title: data.title,
322+ title: loaderData.title,
323 },
324 ];
325}
326```
327
328If you read data from parent matches, update those references too:
329
330```tsx diff
331export function meta({ matches }: Route.MetaArgs) {
332 let rootMatch = matches.find((match) => match.id === "root");
333- let rootData = rootMatch?.data;
334+ let rootData = rootMatch?.loaderData;
335
336 return [{ title: rootData?.siteTitle }];
337}
338```
339
340Replace `data` with `loaderData` on `useMatches()` calls:
341
342```tsx diff
343export default function Component({ matches, loaderData }: ComponentProps) {
344 let matches = useMatches();
345- const rootLoaderData = matches[0].data;
346+ const rootLoaderData = matches[0].loaderData;
347 // ...
348}
349```
350
351### `react-router-dom`
352
353[MODES: framework, data, declarative]
354
355<br/>
356<br/>
357
358**Background**
359
360React Router v8 removes the `react-router-dom` re-export package. In v8, you should import DOM-specific APIs from `react-router/dom` and everything else from `react-router`.
361
362👉 **Update your Code**
363
364Uninstall `react-router-dom`:
365
366```sh
367npm uninstall react-router-dom
368```
369
370Replace `react-router-dom` imports with `react-router` imports:
371
372```tsx diff
373-import { Link, useLocation } from "react-router-dom";
374+import { Link, useLocation } from "react-router";
375```
376
377For DOM-specific APIs, import from `react-router/dom`:
378
379```tsx diff
380-import { RouterProvider } from "react-router-dom";
381+import { RouterProvider } from "react-router/dom";
382```
383
384### Cloudflare Vite Plugin
385
386[MODES: framework]
387
388<br/>
389<br/>
390
391**Background**
392
393React Router v8 removes the React Router Cloudflare dev proxy. Cloudflare projects should use [`@cloudflare/vite-plugin`][cloudflare-vite-plugin] instead.
394
395👉 **Update your Code**
396
397Replace `cloudflareDevProxy` with `cloudflare`:
398
399```tsx diff filename=vite.config.ts
400import { reactRouter } from "@react-router/dev/vite";
401-import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
402+import { cloudflare } from "@cloudflare/vite-plugin";
403import { defineConfig } from "vite";
404
405export default defineConfig({
406 plugins: [
407- cloudflareDevProxy(),
408+ cloudflare(),
409 reactRouter(),
410 ],
411});
412```
413
414### `@react-router/architect` `useRequestContextDomainName`
415
416[MODES: framework]
417
418<br/>
419<br/>
420
421**Background**
422
423In v7, the `@react-router/architect` adapter uses `X-Forwarded-Host` when creating the `request`, falling back to the `Host` header. In React Router v8, the adapter uses `event.requestContext.domainName` by default, falling back to the `Host` header.
424
425👉 **Update your Code**
426
427Opt in to the v8 behavior while still on v7 by passing `useRequestContextDomainName: true`:
428
429```ts
430import { createRequestHandler } from "@react-router/architect";
431import * as build from "./build/server";
432
433export const handler = createRequestHandler({
434 build,
435 useRequestContextDomainName: true,
436});
437```
438
439This option is removed in v8 because the `event.requestContext.domainName` behavior is the default.
440
441## Upgrade to v8
442
443Now that your app is caught up, update to v8:
444
445```shellscript nonumber
446# data/declarative mode
447npm install react-router@latest
448
449# framework mode
450npm install react-router@latest @react-router/{dev,node,etc.}@latest
451```
452
453Congratulations, you're now on v8!
454
455[api-development-strategy]: ../community/api-development-strategy
456[observability]: ../how-to/instrumentation
457[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
458[vite-environment]: https://vite.dev/guide/api-environment
459[node-custom-server-template]: https://github.com/remix-run/react-router-templates/blob/7c617a435510bc3add3a5395c07bc65328b65e9e/node-custom-server/vite.config.ts
460[cloudflare-vite-plugin]: https://developers.cloudflare.com/workers/vite-plugin/