UNPKG

11.6 kB Markdown View 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 20+ (if using Node as your runtime)
30- Vite 5+
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 { BrowserRouter } from "react-router";
280import App from "./App";
281
282const router = createBrowserRouter([
283 // ... route definitions
284]);
285
286ReactDOM.createRoot(
287 document.getElementById("root")!,
288).render(
289 <React.StrictMode>
290 <RouterProvider router={router} />;
291 </React.StrictMode>,
292);
293```
294
295You would rename it to `entry.client.tsx` and change it to this:
296
297```tsx filename=src/entry.client.tsx
298import React from "react";
299import ReactDOM from "react-dom/client";
300import { HydratedRouter } from "react-router/dom";
301
302ReactDOM.hydrateRoot(
303 document,
304 <React.StrictMode>
305 <HydratedRouter />
306 </React.StrictMode>,
307);
308```
309
310- Use `hydrateRoot` instead of `createRoot`
311- Render a `<HydratedRouter>` instead of your `<App/>` component
312- Note: We are no longer creating the routes and manually passing them to `<RouterProvider />`. We will migrate our route definitions in the next step.
313
314## 6. Migrate your routes
315
316The 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.
317
318**👉 Move definitions to a `routes.ts` file**
319
320```shellscript nonumber
321touch src/routes.ts src/catchall.tsx
322```
323
324Move 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.
325
326```diff filename=src/routes.ts
327+import type { RouteConfig } from "@react-router/dev/routes";
328
329-const router = createBrowserRouter([
330+export default [
331 {
332 path: "/",
333 lazy: () => import("./routes/layout").then(convert),
334 children: [
335 {
336 index: true,
337 lazy: () => import("./routes/home").then(convert),
338 },
339 {
340 path: "about",
341 lazy: () => import("./routes/about").then(convert),
342 },
343 {
344 path: "todos",
345 lazy: () => import("./routes/todos").then(convert),
346 children: [
347 {
348 path: ":id",
349 lazy: () =>
350 import("./routes/todo").then(convert),
351 },
352 ],
353 },
354 ],
355 },
356-]);
357+] satisfies RouteConfig;
358```
359
360**👉 Replace the `lazy` loader with a `file` loader**
361
362```diff filename=src/routes.ts
363export default [
364 {
365 path: "/",
366- lazy: () => import("./routes/layout").then(convert),
367+ file: "./routes/layout.tsx",
368 children: [
369 {
370 index: true,
371- lazy: () => import("./routes/home").then(convert),
372+ file: "./routes/home.tsx",
373 },
374 {
375 path: "about",
376- lazy: () => import("./routes/about").then(convert),
377+ file: "./routes/about.tsx",
378 },
379 {
380 path: "todos",
381- lazy: () => import("./routes/todos").then(convert),
382+ file: "./routes/todos.tsx",
383 children: [
384 {
385 path: ":id",
386- lazy: () => import("./routes/todo").then(convert),
387+ file: "./routes/todo.tsx",
388 },
389 ],
390 },
391 ],
392 },
393] satisfies RouteConfig;
394```
395
396[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.
397
398## 7. Boot the app
399
400At 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.
401
402**👉 Add `dev` script and run the app**
403
404```json filename=package.json
405"scripts": {
406 "dev": "react-router dev"
407}
408```
409
410Now make sure you can boot your app at this point before moving on:
411
412```shellscript
413npm run dev
414```
415
416You will probably want to add `.react-router/` to your `.gitignore` file to avoid tracking unnecessary files in your repository.
417
418```txt
419.react-router/
420```
421
422You can checkout [Type Safety][type-safety] to learn how to fully setup and use autogenerated type safety for params, loader data, and more.
423
424## Enable SSR and/or Pre-rendering
425
426If 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.
427
428```ts filename=react-router.config.ts
429import type { Config } from "@react-router/dev/config";
430
431export default {
432 ssr: true,
433 async prerender() {
434 return ["/", "/about", "/contact"];
435 },
436} satisfies Config;
437```
438
439[upgrade-component-routes]: ./component-routes
440[configuring-routes]: ../start/framework/routing
441[route-modules]: ../start/framework/route-module
442[type-safety]: ../how-to/route-module-type-safety
443
\No newline at end of file