If you are using <RouterProvider>
please see Framework Adoption from RouterProvider instead.
If you are using <Routes>
this is the right place.
The 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 or Discord.
The Vite plugin adds:
The initial setup requires the most work. However, once complete, you can adopt new features incrementally, one route at a time.
To use the Vite plugin, your project requires:
👉 Install the React Router Vite plugin
npm install -D @react-router/dev
👉 Install a runtime adapter
We will assume you are using Node as your runtime.
npm install @react-router/node
👉 Swap out the React plugin for React Router.
-import react from '@vitejs/plugin-react'
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
- react()
+ reactRouter()
],
});
👉 Create a react-router.config.ts
file
Add 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.
touch react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
appDirectory: "src",
ssr: false,
} satisfies Config;
In 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.
👉 Move your existing index.html
to root.tsx
For example, if your current index.html
looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
You would move that markup into src/root.tsx
and delete index.html
:
touch src/root.tsx
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
export function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>My App</title>
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />;
}
In 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.
👉 Make src/entry.client.tsx
your entry point
If your current src/main.tsx
looks like this:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import "./index.css";
import App from "./App";
ReactDOM.createRoot(
document.getElementById("root")!
).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
You would rename it to entry.client.tsx
and change it to this:
import React from "react";
import ReactDOM from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
import "./index.css";
ReactDOM.hydrateRoot(
document,
<React.StrictMode>
<HydratedRouter />
</React.StrictMode>
);
hydrateRoot
instead of createRoot
<HydratedRouter>
instead of your <App/>
component<App/>
component. We'll bring it back in a later step, but first we want to get the app to boot with the new entry point.Between root.tsx
and entry.client.tsx
, you may want to shuffle some stuff around between them.
In general:
root.tsx
contains any rendering things like context providers, layouts, styles, etc.entry.client.tsx
should be as minimal as possible<App/>
component yet, we'll do that in a later stepNote that your root.tsx
file will be statically generated and served as the entry point of your app, so just that module will need to be compatible with server rendering. This is where most of your trouble will come.
The React Router Vite plugin uses a routes.ts
file to configure your routes. For now we'll add a simple catchall route to get things going.
👉 Setup a catchall.tsx
route
touch src/routes.ts src/catchall.tsx
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
// * matches all URLs, the ? makes it optional so it will match / as well
route("*?", "catchall.tsx"),
] satisfies RouteConfig;
👉 Render a placeholder route
Eventually we'll replace this with our original App
component, but for now we'll just render something simple to make sure we can boot the app.
export default function Component() {
return <div>Hello, world!</div>;
}
View our guide on configuring routes to learn more about the routes.ts
file.
At this point you should be able to to boot the app and see the root layout.
👉 Add dev
script and run the app
"scripts": {
"dev": "react-router dev"
}
Now make sure you can boot your app at this point before moving on:
npm run dev
To get back to rendering your app, we'll update the "catchall" route we setup earlier that matches all URLs so that your existing <Routes>
get a chance to render.
👉 Update the catchall route to render your app
import App from "./App";
export default function Component() {
return <App />;
}
Your app should be back on the screen and working as usual!
You can now incrementally migrate your routes to route modules.
Given an existing route like this:
// ...
import About from "./containers/About";
export default function App() {
return (
<Routes>
<Route path="/about" element={<About />} />
</Routes>
);
}
👉 Add the route definition to routes.ts
import {
type RouteConfig,
route,
} from "@react-router/dev/routes";
export default [
route("/about", "./pages/about.tsx"),
route("*?", "catchall.tsx"),
] satisfies RouteConfig;
👉 Add the route module
Edit the route module to use the Route Module API:
export async function clientLoader() {
// you can now fetch data here
return {
title: "About page",
};
}
export default function Component({ loaderData }) {
return <h1>{loaderData.title}</h1>;
}
See Type Safety to setup autogenerated type safety for params, loader data, and more.
The first few routes you migrate are the hardest because you often have to access various abstractions a bit differently than before (like in a loader instead of from a hook or context). But once the trickiest bits get dealt with, you get into an incremental groove.
If 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. See Deploying for more information.
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
async prerender() {
return ["/", "/about", "/contact"];
},
} satisfies Config;