UNPKG

14.2 kB Markdown View 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
16React Router v8 requires the following minimum versions. Update them before updating React Router to v8:
17
18- `node@22.22+`
19- `react@19.2.7+`/`react-dom@19.2.7+`
20
21Framework mode also requires:
22
23- `vite@7+` (requires `future.v8_viteEnvironmentApi`)
24 - also make sure any custom Vite plugins or config are compatible with Vite 7
25
26## Update to latest v7.x
27
28Before 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.
29
30👉 Update to latest v7
31
32```sh
33npm install react-router@7 @react-router/{dev,node,etc.}@7
34```
35
36## Future Flags
37
38### `future.v8_middleware`
39
40[MODES: framework, data]
41
42**Background**
43
44Middleware 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.
45
46👉 **Enable the Flag**
47
48In Framework mode:
49
50```ts filename=react-router.config.ts
51import type { Config } from "@react-router/dev/config";
52
53export default {
54 future: {
55 v8_middleware: true,
56 },
57} satisfies Config;
58```
59
60In Data mode:
61
62```ts
63import { createBrowserRouter } from "react-router";
64
65const router = createBrowserRouter(routes, {
66 future: {
67 v8_middleware: true,
68 },
69});
70```
71
72**Update your Code**
73
74If you're using the `context` parameter in `loader` and `action` functions, you may need to update your code:
75
76- 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).
77- 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.
78
79### `future.v8_splitRouteModules`
80
81[MODES: framework]
82
83**Background**
84
85This 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.
86
87This 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).
88
89👉 **Enable the Flag**
90
91```ts filename=react-router.config.ts
92import type { Config } from "@react-router/dev/config";
93
94export default {
95 future: {
96 v8_splitRouteModules: true,
97 },
98} satisfies Config;
99```
100
101**Update your Code**
102
103No code changes are required. This is an optimization feature that works automatically once enabled.
104
105### `future.v8_viteEnvironmentApi`
106
107[MODES: framework]
108
109**Background**
110
111This 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+.
112
113👉 **Enable the Flag**
114
115```ts filename=react-router.config.ts
116import type { Config } from "@react-router/dev/config";
117
118export default {
119 future: {
120 v8_viteEnvironmentApi: true,
121 },
122} satisfies Config;
123```
124
125**Update your Code**
126
127Most 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.
128
129For example, a custom server build should move its SSR `rollupOptions` from the top-level `build` config into `environments.ssr.build`:
130
131```tsx diff filename=vite.config.ts
132import { reactRouter } from "@react-router/dev/vite";
133import { defineConfig } from "vite";
134
135-export default defineConfig(({ isSsrBuild }) => ({
136- build: {
137- rollupOptions: isSsrBuild
138- ? {
139- input: "./server/app.ts",
140- }
141- : undefined,
142- },
143+export default defineConfig({
144+ environments: {
145+ ssr: {
146+ build: {
147+ rollupOptions: {
148+ input: "./server/app.ts",
149+ },
150+ },
151+ },
152+ },
153 plugins: [reactRouter()],
154-}));
155+});
156```
157
158See the [`node-custom-server` template][node-custom-server-template] for a complete example.
159
160### `future.v8_passThroughRequests`
161
162[MODES: framework]
163
164**Background**
165
166By 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`.
167
168This flag eliminates that normalization and passes the raw HTTP `request` instance to your handlers. This provides a few benefits:
169
170- Reduces server-side overhead by eliminating multiple `new Request()` calls on the critical path
171- Allows you to distinguish document from data requests in your handlers based on the presence of a `.data` suffix (useful for [observability] purposes)
172
173If 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 with React Router's internal implementation details removed.
174
175👉 **Enable the Flag**
176
177```ts filename=react-router.config.ts
178import type { Config } from "@react-router/dev/config";
179
180export default {
181 future: {
182 v8_passThroughRequests: true,
183 },
184} satisfies Config;
185```
186
187**Update your Code**
188
189If your code relies on inspecting the request URL, you should review it for any assumptions about the URL format:
190
191```tsx
192// ❌ Before: assuming no `.data` suffix in `request.url` pathname
193export async function loader({
194 request,
195}: Route.LoaderArgs) {
196 let url = new URL(request.url);
197 if (url.pathname === "/path") {
198 // This check might now behave differently because the request pathname will
199 // contain the `.data` suffix on data requests
200 }
201}
202
203// ✅ After: use `url` with React Router's internal details removed
204// and `request.url` for the original request URL
205export async function loader({
206 request,
207 url,
208}: Route.LoaderArgs) {
209 if (url.pathname === "/path") {
210 // This will always have the `.data` suffix stripped
211 }
212
213 // And now you can distinguish between document versus data requests
214 let isDataRequest = new URL(
215 request.url,
216 ).pathname.endsWith(".data");
217}
218```
219
220### `future.v8_trailingSlashAwareDataRequests`
221
222[MODES: framework]
223
224**Background**
225
226React 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.
227
228In v7, your HTTP and `request` pathnames are as follows for `/a/b/c` and `/a/b/c/`
229
230| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
231| ------------ | ----------------- | ----------------------- |
232| **Document** | `/a/b/c` | `/a/b/c` ✅ |
233| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
234
235| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
236| ------------- | ----------------- | ----------------------- |
237| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
238| **Data** | `/a/b/c.data` | `/a/b/c` ⚠️ |
239
240With this flag enabled, these pathnames are made consistent through a new `_.data` format for client-side `.data` requests:
241
242| URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
243| ------------ | ----------------- | ----------------------- |
244| **Document** | `/a/b/c` | `/a/b/c` ✅ |
245| **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
246
247| URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
248| ------------- | ------------------ | ----------------------- |
249| **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
250| **Data** | `/a/b/c/_.data` ⬅️ | `/a/b/c/` ✅ |
251
252This flag also aligns the root data request to match this behavior by changing it from `/_root.data` to `/_.data`.
253
254👉 **Enable the Flag**
255
256```ts filename=react-router.config.ts
257import type { Config } from "@react-router/dev/config";
258
259export default {
260 future: {
261 v8_trailingSlashAwareDataRequests: true,
262 },
263} satisfies Config;
264```
265
266**Update your Code**
267
268If 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.
269
270## Other Breaking Changes
271
272The changes in this section are not controlled by future flags, but you can update your code while still on v7 before moving to v8.
273
274### `meta`/`matches` `data` Values
275
276[MODES: framework]
277
278**Background**
279
280React Router v8 removed deprecated `data` fields in favor of `loaderData` in a few places:
281
282- `meta` function `data` argument
283- `meta` function `matches` argument (`matches[i].data`)
284- `useMatches()` (`matches[i].data`)
285
286Use `loaderData` instead on `MetaArgs`, each item in `MetaArgs.matches`, and each match returned from `useMatches()`
287
288👉 **Update your Code**
289
290Replace `data` with `loaderData` in your `meta` functions:
291
292```tsx diff
293export function meta({
294- data,
295+ loaderData,
296 matches,
297}: Route.MetaArgs) {
298 return [
299 {
300- title: data.title,
301+ title: loaderData.title,
302 },
303 ];
304}
305```
306
307If you read data from parent matches, update those references too:
308
309```tsx diff
310export function meta({ matches }: Route.MetaArgs) {
311 let rootMatch = matches.find((match) => match.id === "root");
312- let rootData = rootMatch?.data;
313+ let rootData = rootMatch?.loaderData;
314
315 return [{ title: rootData?.siteTitle }];
316}
317```
318
319Replace `data` with `loaderData` on `useMatches()` calls:
320
321```tsx diff
322export default function Component({ matches, loaderData }: ComponentProps) {
323 let matches = useMatches();
324- const rootLoaderData = matches[0].data;
325+ const rootLoaderData = matches[0].loaderData;
326 // ...
327}
328```
329
330### `react-router-dom`
331
332[MODES: framework, data, declarative]
333
334**Background**
335
336React 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`.
337
338👉 **Update your Code**
339
340Uninstall `react-router-dom`:
341
342```sh
343npm uninstall react-router-dom
344```
345
346Replace `react-router-dom` imports with `react-router` imports:
347
348```tsx diff
349-import { Link, useLocation } from "react-router-dom";
350+import { Link, useLocation } from "react-router";
351```
352
353For DOM-specific APIs, import from `react-router/dom`:
354
355```tsx diff
356-import { RouterProvider } from "react-router-dom";
357+import { RouterProvider } from "react-router/dom";
358```
359
360### Cloudflare Vite Plugin
361
362[MODES: framework]
363
364**Background**
365
366React Router v8 removes the React Router Cloudflare dev proxy. Cloudflare projects should use [`@cloudflare/vite-plugin`][cloudflare-vite-plugin] instead.
367
368👉 **Update your Code**
369
370Replace `cloudflareDevProxy` with `cloudflare`:
371
372```tsx diff filename=vite.config.ts
373import { reactRouter } from "@react-router/dev/vite";
374-import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
375+import { cloudflare } from "@cloudflare/vite-plugin";
376import { defineConfig } from "vite";
377
378export default defineConfig({
379 plugins: [
380- cloudflareDevProxy(),
381+ cloudflare(),
382 reactRouter(),
383 ],
384});
385```
386
387### `@react-router/architect` `useRequestContextDomainName`
388
389[MODES: framework]
390
391**Background**
392
393In 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.
394
395👉 **Update your Code**
396
397Opt in to the v8 behavior while still on v7 by passing `useRequestContextDomainName: true`:
398
399```ts
400import { createRequestHandler } from "@react-router/architect";
401import * as build from "./build/server";
402
403export const handler = createRequestHandler({
404 build,
405 useRequestContextDomainName: true,
406});
407```
408
409This option is removed in v8 because the `event.requestContext.domainName` behavior is the default.
410
411## Upgrade to v8
412
413Now that your app is caught up, update to v8:
414
415```shellscript nonumber
416# data/declarative mode
417npm install react-router@latest
418
419# framework mode
420npm install react-router@latest @react-router/{dev,node,etc.}@latest
421```
422
423Congratulations, you're now on v8!
424
425[api-development-strategy]: ../community/api-development-strategy
426[observability]: ../how-to/instrumentation
427[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
428[vite-environment]: https://vite.dev/guide/api-environment
429[node-custom-server-template]: https://github.com/remix-run/react-router-templates/blob/7c617a435510bc3add3a5395c07bc65328b65e9e/node-custom-server/vite.config.ts
430[cloudflare-vite-plugin]: https://developers.cloudflare.com/workers/vite-plugin/
431
\No newline at end of file