UNPKG

11.6 kBMarkdownView Raw
1---
2title: Framework Adoption from RouterProvider
3order: 5
4---
5
6# Framework Adoption from RouterProvider
7
8If you are not using `<RouterProvider>` please see [Framework Adoption from Component Routes][upgrade-component-routes] instead.
9
10The React Router Vite plugin adds framework features to React Router. This guide will help you adopt the plugin in your app. If you run into any issues, please reach out for help on [Twitter](https://x.com/remix_run) or [Discord](https://rmx.as/discord).
11
12## Features
13
14The Vite plugin adds:
15
16- Route loaders, actions, and automatic data revalidation
17- Type-safe Routes Modules
18- Automatic route code-splitting
19- Automatic scroll restoration across navigations
20- Optional Static pre-rendering
21- Optional Server rendering
22
23The initial setup requires the most work. However, once complete, you can adopt new features incrementally.
24
25## Prerequisites
26
27To use the Vite plugin, your project requires:
28
29- Node.js 22.22.0+
30- Vite 7+ or Vite 8+
31
32## 1. Move route definitions into route modules
33
34The React Router Vite plugin renders its own `RouterProvider`, so you can't render an existing `RouterProvider` within it. Instead, you will need to format all of your route definitions to match the [Route Module API][route-modules].
35
36This step will take the longest, however there are several benefits to doing this regardless of adopting the React Router Vite plugin:
37
38- Route modules will be lazy loaded, decreasing the initial bundle size of your app
39- Route definitions will be uniform, simplifying your app's architecture
40- Moving to route modules is incremental, you can migrate one route at a time
41
42**👉 Move your route definitions into route modules**
43
44Export each piece of your route definition as a separate named export, following the [Route Module API][route-modules].
45
46```tsx filename=src/routes/about.tsx
47export async function clientLoader() {
48 return {
49 title: "About",
50 };
51}
52
53export default function About() {
54 let data = useLoaderData();
55 return <div>{data.title}</div>;
56}
57
58// clientAction, ErrorBoundary, etc.
59```
60
61**👉 Create a convert function**
62
63Create a helper function to convert route module definitions into the format expected by your data router:
64
65```tsx filename=src/main.tsx
66function convert(m: any) {
67 let {
68 clientLoader,
69 clientAction,
70 default: Component,
71 ...rest
72 } = m;
73 return {
74 ...rest,
75 loader: clientLoader,
76 action: clientAction,
77 Component,
78 };
79}
80```
81
82**👉 Lazy load and convert your route modules**
83
84Instead of importing your route modules directly, lazy load and convert them to the format expected by your data router.
85
86Not only does your route definition now conform to the Route Module API, but you also get the benefits of code-splitting your routes.
87
88```diff filename=src/main.tsx
89let router = createBrowserRouter([
90 // ... other routes
91 {
92 path: "about",
93- loader: aboutLoader,
94- Component: About,
95+ lazy: () => import("./routes/about").then(convert),
96 },
97 // ... other routes
98]);
99```
100
101Repeat this process for each route in your app.
102
103## 2. Install the Vite plugin
104
105Once all of your route definitions are converted to route modules, you can adopt the React Router Vite plugin.
106
107**👉 Install the React Router Vite plugin**
108
109```shellscript nonumber
110npm install -D @react-router/dev
111```
112
113**👉 Install a runtime adapter**
114
115We will assume you are using Node as your runtime.
116
117```shellscript nonumber
118npm install @react-router/node
119```
120
121**👉 Swap out the React plugin for React Router**
122
123```diff filename=vite.config.ts
124-import react from '@vitejs/plugin-react'
125+import { reactRouter } from "@react-router/dev/vite";
126import { defineConfig } from "vite";
127
128
129export default defineConfig({
130 plugins: [
131- react()
132+ reactRouter()
133 ],
134});
135```
136
137## 3. Add the React Router config
138
139**👉 Create a `react-router.config.ts` file**
140
141Add the following to the root of your project. In this config you can tell React Router about your project, like where to find the app directory and to not use SSR (server-side rendering) for now.
142
143```shellscript nonumber
144touch react-router.config.ts
145```
146
147```ts filename=react-router.config.ts
148import type { Config } from "@react-router/dev/config";
149
150export default {
151 appDirectory: "src",
152 ssr: false,
153} satisfies Config;
154```
155
156## 4. Add the Root entry point
157
158In a typical Vite app, the `index.html` file is the entry point for bundling. The React Router Vite plugin moves the entry point to a `root.tsx` file so you can use React to render the shell of your app instead of static HTML, and eventually upgrade to Server Rendering if you want.
159
160**👉 Move your existing `index.html` to `root.tsx`**
161
162For example, if your current `index.html` looks like this:
163
164```html filename=index.html
165<!DOCTYPE html>
166<html lang="en">
167 <head>
168 <meta charset="UTF-8" />
169 <meta
170 name="viewport"
171 content="width=device-width, initial-scale=1.0"
172 />
173 <title>My App</title>
174 </head>
175 <body>
176 <div id="root"></div>
177 <script type="module" src="/src/main.tsx"></script>
178 </body>
179</html>
180```
181
182You would move that markup into `src/root.tsx` and delete `index.html`:
183
184```shellscript nonumber
185touch src/root.tsx
186```
187
188```tsx filename=src/root.tsx
189import {
190 Links,
191 Meta,
192 Outlet,
193 Scripts,
194 ScrollRestoration,
195} from "react-router";
196
197export function Layout({
198 children,
199}: {
200 children: React.ReactNode;
201}) {
202 return (
203 <html lang="en">
204 <head>
205 <meta charSet="UTF-8" />
206 <meta
207 name="viewport"
208 content="width=device-width, initial-scale=1.0"
209 />
210 <title>My App</title>
211 <Meta />
212 <Links />
213 </head>
214 <body>
215 {children}
216 <ScrollRestoration />
217 <Scripts />
218 </body>
219 </html>
220 );
221}
222
223export default function Root() {
224 return <Outlet />;
225}
226```
227
228**👉 Move everything above `RouterProvider` to `root.tsx`**
229
230Any global styles, context providers, etc. should be moved into `root.tsx` so they can be shared across all routes.
231
232For example, if your `App.tsx` looks like this:
233
234```tsx filename=src/App.tsx
235import "./index.css";
236
237export default function App() {
238 return (
239 <OtherProviders>
240 <AppLayout>
241 <RouterProvider router={router} />
242 </AppLayout>
243 </OtherProviders>
244 );
245}
246```
247
248You would move everything above the `RouterProvider` into `root.tsx`.
249
250```diff filename=src/root.tsx
251+import "./index.css";
252
253// ... other imports and Layout
254
255export default function Root() {
256 return (
257+ <OtherProviders>
258+ <AppLayout>
259 <Outlet />
260+ </AppLayout>
261+ </OtherProviders>
262 );
263}
264```
265
266## 5. Add client entry module (optional)
267
268In the typical Vite app the `index.html` file points to `src/main.tsx` as the client entry point. React Router uses a file named `src/entry.client.tsx` instead.
269
270If no `entry.client.tsx` exists, the React Router Vite plugin will use a default, hidden one.
271
272**👉 Make `src/entry.client.tsx` your entry point**
273
274If your current `src/main.tsx` looks like this:
275
276```tsx filename=src/main.tsx
277import React from "react";
278import ReactDOM from "react-dom/client";
279import {
280 createBrowserRouter,
281 RouterProvider,
282} from "react-router";
283import App from "./App";
284
285const router = createBrowserRouter([
286 // ... route definitions
287]);
288
289ReactDOM.createRoot(
290 document.getElementById("root")!,
291).render(
292 <React.StrictMode>
293 <RouterProvider router={router} />;
294 </React.StrictMode>,
295);
296```
297
298You would rename it to `entry.client.tsx` and change it to this:
299
300```tsx filename=src/entry.client.tsx
301import React from "react";
302import ReactDOM from "react-dom/client";
303import { HydratedRouter } from "react-router/dom";
304
305ReactDOM.hydrateRoot(
306 document,
307 <React.StrictMode>
308 <HydratedRouter />
309 </React.StrictMode>,
310);
311```
312
313- Use `hydrateRoot` instead of `createRoot`
314- Render a `<HydratedRouter>` instead of your `<App/>` component
315- Note: We are no longer creating the routes and manually passing them to `<RouterProvider />`. We will migrate our route definitions in the next step.
316
317## 6. Migrate your routes
318
319The React Router Vite plugin uses a `routes.ts` file to configure your routes. The format will be pretty similar to the definitions of your data router.
320
321**👉 Move definitions to a `routes.ts` file**
322
323```shellscript nonumber
324touch src/routes.ts src/catchall.tsx
325```
326
327Move your route definitions to `routes.ts`. Note that the schemas don't match exactly, so you will get type errors; we'll fix this next.
328
329```diff filename=src/routes.ts
330+import type { RouteConfig } from "@react-router/dev/routes";
331
332-const router = createBrowserRouter([
333+export default [
334 {
335 path: "/",
336 lazy: () => import("./routes/layout").then(convert),
337 children: [
338 {
339 index: true,
340 lazy: () => import("./routes/home").then(convert),
341 },
342 {
343 path: "about",
344 lazy: () => import("./routes/about").then(convert),
345 },
346 {
347 path: "todos",
348 lazy: () => import("./routes/todos").then(convert),
349 children: [
350 {
351 path: ":id",
352 lazy: () =>
353 import("./routes/todo").then(convert),
354 },
355 ],
356 },
357 ],
358 },
359-]);
360+] satisfies RouteConfig;
361```
362
363**👉 Replace the `lazy` loader with a `file` loader**
364
365```diff filename=src/routes.ts
366export default [
367 {
368 path: "/",
369- lazy: () => import("./routes/layout").then(convert),
370+ file: "./routes/layout.tsx",
371 children: [
372 {
373 index: true,
374- lazy: () => import("./routes/home").then(convert),
375+ file: "./routes/home.tsx",
376 },
377 {
378 path: "about",
379- lazy: () => import("./routes/about").then(convert),
380+ file: "./routes/about.tsx",
381 },
382 {
383 path: "todos",
384- lazy: () => import("./routes/todos").then(convert),
385+ file: "./routes/todos.tsx",
386 children: [
387 {
388 path: ":id",
389- lazy: () => import("./routes/todo").then(convert),
390+ file: "./routes/todo.tsx",
391 },
392 ],
393 },
394 ],
395 },
396] satisfies RouteConfig;
397```
398
399[View our guide on configuring routes][configuring-routes] to learn more about the `routes.ts` file and helper functions to further simplify the route definitions.
400
401## 7. Boot the app
402
403At this point you should be fully migrated to the React Router Vite plugin. Go ahead and update your `dev` script and run the app to make sure everything is working.
404
405**👉 Add `dev` script and run the app**
406
407```json filename=package.json
408"scripts": {
409 "dev": "react-router dev"
410}
411```
412
413Now make sure you can boot your app at this point before moving on:
414
415```shellscript
416npm run dev
417```
418
419You will probably want to add `.react-router/` to your `.gitignore` file to avoid tracking unnecessary files in your repository.
420
421```txt
422.react-router/
423```
424
425You can checkout [Type Safety][type-safety] to learn how to fully setup and use autogenerated type safety for params, loader data, and more.
426
427## Enable SSR and/or Pre-rendering
428
429If you want to enable server rendering and static pre-rendering, you can do so with the `ssr` and `prerender` options in the bundler plugin. For SSR you'll need to also deploy the server build to a server.
430
431```ts filename=react-router.config.ts
432import type { Config } from "@react-router/dev/config";
433
434export default {
435 ssr: true,
436 async prerender() {
437 return ["/", "/about", "/contact"];
438 },
439} satisfies Config;
440```
441
442[upgrade-component-routes]: ./component-routes
443[configuring-routes]: ../start/framework/routing
444[route-modules]: ../start/framework/route-module
445[type-safety]: ../how-to/route-module-type-safety