| name | react-router-v7 |
| description | React Router v7 best practices for data-driven routing. Use when implementing routes, loaders, actions, Form components, fetchers, navigation guards, protected routes, or URL search params. Triggers on createBrowserRouter, RouterProvider, useLoaderData, useActionData, useFetcher, NavLink, Outlet. |
React Router v7 Best Practices
Quick Reference
Router Setup (Data Mode):
import { createBrowserRouter, RouterProvider } from "react-router";
const router = createBrowserRouter([
{
path: "/",
Component: Root,
ErrorBoundary: RootErrorBoundary,
loader: rootLoader,
children: [
{ index: true, Component: Home },
{ path: "products/:productId", Component: Product, loader: productLoader },
],
},
]);
ReactDOM.createRoot(root).render(<RouterProvider router={router} />);
Framework Mode (Vite plugin):
import { index, route } from "@react-router/dev/routes";
export default [
index("./home.tsx"),
route("products/:pid", "./product.tsx"),
];
Route Configuration
Nested Routes with Outlets
createBrowserRouter([
{
path: "/dashboard",
Component: Dashboard,
children: [
{ index: true, Component: DashboardHome },
{ path: "settings", Component: Settings },
],
},
]);
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet /> {/* Renders child routes */}
</div>
);
}
Dynamic Segments and Splats
{ path: "teams/:teamId" }
{ path: ":lang?/categories" }
{ path: "files/*" }
Key Decision Points
Form vs Fetcher
Use <Form>: Creating/deleting with URL change, adding to history
Use useFetcher: Inline updates, list operations, popovers - no URL change
Loader vs useEffect
Use loader: Data before render, server-side fetch, automatic revalidation
Use useEffect: Client-only data, user-interaction dependent, subscriptions
Gates (decision sequencing)
Answer in order. Pass means the condition is true; pick the API on the same line and stop.
<Form> vs useFetcher
- Must the URL or history stack change (bookmark/share, back returns to prior screen)?
- Pass →
<Form> / route action (or useSubmit + navigation). Stop.
- Fail → Step 2.
- Mutation stays on the same route (inline edit, modal, list row, no address change)?
- Pass →
useFetcher(). Stop.
- Fail → Re-check step 1; you may need a dedicated action route or POST to the current URL.
loader vs useEffect
- Is data needed for correct first render (or your intended
<Suspense> boundary) for this route?
- Pass →
loader (Framework: clientLoader when appropriate). Stop.
- Fail → Step 2.
- Fetch only after mount from user action, timer, or subscription (not route entry)?
- Pass →
useEffect / event handlers. Stop.
- Fail → Prefer loader + revalidation over an effect that mirrors navigation.
Additional Documentation
Mode Comparison
| Feature | Framework Mode | Data Mode | Declarative Mode |
|---|
| Setup | Vite plugin | createBrowserRouter | <BrowserRouter> |
| Type Safety | Auto-generated types | Manual | Manual |
| SSR Support | Built-in | Manual | Limited |
| Use Case | Full-stack apps | SPAs with control | Simple/legacy |