Router Types
Router Types
React Router ships multiple router implementations. Each is optimised for a different runtime environment or use case. Choosing the right one matters.
BrowserRouter
Uses the HTML5 History API (pushState, replaceState). URLs look clean: /about. Requires server-side fallback config to handle direct URL hits.
BrowserRouter wraps the app in a context that listens to window.history events. When the user clicks a <Link>, it calls history.pushState(), updating the URL without a page load, then re-renders the matched <Route> components.
Server config required: If a user visits example.com/about directly, the server must return your index.html for every route (not a 404). Configure your Nginx/Apache/Vite dev server accordingly.
HashRouter
Uses the URL hash fragment: /#/about. Works without any server config because the hash is never sent to the server. Good for static file hosting.
main.jsx: HashRouter
import { HashRouter } from 'react-router-dom';// URL will look like;http://example.com/#/about// The server only ever sees: http://example.com/createRoot(root).render( <HashRouter><App /></HashRouter> );
MemoryRouter
Keeps the navigation state in memory, no URL changes at all. Perfect for tests, React Native, Storybook, or embedded widgets.
Component.test.jsx: MemoryRouter in tests
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
it('renders profile page', () => {
render(
<MemoryRouter initialEntries={['/profile/7']}>
<App />
</MemoryRouter>
);
// assert profile renders for id=7
});
StaticRouter
Used on the server during Server-Side Rendering (SSR). It never changes location you pass the current URL as a prop from the server request.
server.js: Express + StaticRouter
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
app.get('*', (req, res) => {
const html = renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});
RouterProvider
The v6.4+ data router API. Pairs with createBrowserRouter. Enables loaders, actions, deferred data, and error boundaries at the route level.
The RouterProvider + createBrowserRouter pattern is now the recommended approach. It unlocks loader functions that fetch data before rendering, action functions that handle form mutations, and route-level error boundaries.
main.jsx: RouterProvider (recommended)
import {
createBrowserRouter,
RouterProvider,
} from 'react-router-dom';
import Home from './pages/Home';
import Profile, { profileLoader } from './pages/Profile';
const router = createBrowserRouter([
{ path: '/', element: <Home /> },
{
path: '/profile/:id',
element: <Profile />,
loader: profileLoader, // fetches data BEFORE render
errorElement: <ErrorPage />,
},
]);
createRoot(root).render(
<RouterProvider router={router} />
);
Profile.jsx — using a loader
import { useLoaderData } from 'react-router-dom';
// Runs on the router, before the component mounts
export async function profileLoader({ params }) {
const res = await fetch(`/api/users/${params.id}`);
if (!res.ok) throw new Response('Not Found', { status: 404 });
return res.json();
}
export default function Profile() {
const user = useLoaderData();
return <h1>{user.name}</h1>;
}
NativeRouter
Designed for React Native apps. Uses the native navigation history without depending on the browser DOM or History API.
references:
