React Router 02 ⚛️ Root Provider — Configuring createBrowserRouter
The previous chapter installed the react-router package. This chapter configures the root provider that connects the router to the React tree. In Data mode, the entry point is createBrowserRouter, which accepts an array of route objects and returns a router instance. That instance is passed to <RouterProvider>, which renders the matching route components and manages the navigation state. The critical detail is the import path: createBrowserRouter comes from react-router, but RouterProvider must come from react-router/dom in a browser environment. Importing RouterProvider from the wrong path omits the react-dom flushSync integration, which can cause subtle rendering bugs in navigations .
Key point: createBrowserRouter creates the router from an array of route objects. <RouterProvider router={router} /> renders it. The route objects define the paths, the components, the loaders, and the error boundaries. The RouterProvider must be imported from react-router/dom for browser applications.
Why the root provider requires a specific configuration
The import path problem. The RouterProvider is exported from both react-router and react-router/dom. The difference is that the react-router/dom export automatically wires up react-dom‘s flushSync implementation, which is required for correct synchronous updates during navigation . In a browser environment, importing from react-router directly works for rendering, but the navigation updates may not flush synchronously, which can cause visual glitches or stale UI. The react-router/dom import is the correct choice for any application that renders in the browser. The bare react-router import is reserved for non-DOM environments like Node.js tests .
The route object problem. createBrowserRouter accepts an array of route objects. Each object has a path, a Component (or element), and optionally children, loader, action, and ErrorBoundary. The Component property is the React component that renders when the path matches. The element property is an alternative that accepts a JSX element directly. The children array nests routes, and the child routes render into the parent’s <Outlet /> .
The root route problem. The root route is the top-level entry in the array. It typically has path: "/" and a component that renders the layout. The layout component contains the <Outlet />, which is the placeholder for the child routes. Without the <Outlet />, the child routes never render. The root route can also have an ErrorBoundary that catches errors from any child route .
The basename problem. The createBrowserRouter function accepts a second argument with options. The basename option sets the base URL for the application. This is required when the application is deployed to a subdirectory (e.g., /app) rather than the domain root. The <Link to="/" /> resolves to /app/ when the basename is /app .
The trade-off. The declarative <BrowserRouter> is simpler for small applications that do not need data loading. The createBrowserRouter is more complex to set up but provides the data APIs (loader, action, useLoaderData) that make the application more robust. The exam tests the configuration of the router and the distinction between the two modes.
a. Creating the router and the root route
The createBrowserRouter function is imported from react-router. The route array defines the root route and its children.
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
function Root() {
return (
<div>
<h1>My App</h1>
<Outlet />
</div>
);
}
const router = createBrowserRouter([
{
path: "/",
Component: Root,
children: [
{ index: true, Component: Home },
{ path: "about", Component: About },
],
},
]);
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
The index: true property marks the default child route. It renders at the parent’s path (/) when no other child matches. The path: "about" creates the /about URL. The <Outlet /> in the Root component is where the child routes render .
The Component property is the uppercase form. The lowercase element property is the alternative that accepts a JSX element. The Component form is preferred in React Router v7 and v8 because it allows the router to handle the component rendering more efficiently .
b. The RouterProvider and the DOM import
The RouterProvider is the component that renders the router. It is imported from react-router/dom in a browser environment.
import { RouterProvider } from "react-router/dom";
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
The router prop is the object returned by createBrowserRouter. The RouterProvider subscribes to the router’s state and renders the matching route components. It also provides the context that the hooks (useNavigate, useParams, useLoaderData) use to access the router state .
The RouterProvider accepts a fallbackElement prop that renders while the router is initializing. This is useful for server-side rendering or for applications that perform asynchronous initialization before the first render.
<RouterProvider router={router} fallbackElement={<Spinner />} />
c. Error boundaries and the root error handler
The root route can define an ErrorBoundary that catches errors from any child route. The useRouteError hook provides the error object inside the boundary component.
import { useRouteError } from "react-router";
function RootErrorBoundary() {
const error = useRouteError();
return (
<div>
<h1>Something went wrong</h1>
<p>{error.statusText || error.message}</p>
</div>
);
}
const router = createBrowserRouter([
{
path: "/",
Component: Root,
ErrorBoundary: RootErrorBoundary,
children: [...],
},
]);
The ErrorBoundary catches errors from the loaders, the actions, and the component rendering. Without it, an error in a loader crashes the entire application. The boundary is the mechanism for graceful error handling .
Complete Example Session
# ============================================
# PART 1: IMPORT THE ROUTER FUNCTIONS
# ============================================
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { Outlet } from "react-router";
# ============================================
# PART 2: DEFINE THE ROOT COMPONENT
# ============================================
function Root() {
return (
<div>
<nav>Navigation</nav>
<Outlet />
</div>
);
}
# ============================================
# PART 3: DEFINE THE CHILD COMPONENTS
# ============================================
function Home() {
return <h1>Home</h1>;
}
function About() {
return <h1>About</h1>;
}
# ============================================
# PART 4: CREATE THE ROUTER
# ============================================
const router = createBrowserRouter([
{
path: "/",
Component: Root,
children: [
{ index: true, Component: Home },
{ path: "about", Component: About },
],
},
]);
# ============================================
# PART 5: RENDER THE ROUTER PROVIDER
# ============================================
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
# ============================================
# PART 6: ADD AN ERROR BOUNDARY
# ============================================
function RootErrorBoundary() {
const error = useRouteError();
return <h1>Error: {error.message}</h1>;
}
const router = createBrowserRouter([
{
path: "/",
Component: Root,
ErrorBoundary: RootErrorBoundary,
children: [...],
},
]);
# ============================================
# PART 7: ADD A BASENAME
# ============================================
const router = createBrowserRouter(
[
{
path: "/",
Component: Root,
children: [...],
},
],
{ basename: "/app" }
);
# ============================================
# PART 8: VERIFY THE ROUTES
# ============================================
# Navigate to http://localhost:5173/
# The Root component renders, with the Home component in the Outlet.
# Navigate to http://localhost:5173/about
# The Root component renders, with the About component in the Outlet.
The eight parts trace the root provider configuration: imports, root component, child components, router creation, provider rendering, error boundary, basename, and verification.
Quick Reference
Imports
| Function | Import From |
|---|---|
createBrowserRouter | react-router |
RouterProvider | react-router/dom |
Outlet | react-router |
useRouteError | react-router |
useLoaderData | react-router |
Route Object Properties
| Property | Purpose |
|---|---|
path | URL pattern |
Component | React component |
element | JSX element (alternative) |
children | Nested routes |
index | Default child route |
loader | Data loading function |
action | Form submission handler |
ErrorBoundary | Error handling component |
Router Options
| Option | Purpose |
|---|---|
basename | Base URL path |
hydrationData | SSR hydration data |
patchRoutesOnNavigation | Lazy route discovery |
Component vs Element
| Property | Value |
|---|---|
Component | { Home } (function reference) |
element | <Home /> (JSX element) |
Best Practices
✅ Do This:
import { RouterProvider } from "react-router/dom";
<!-- Use the DOM-specific import in browsers --> # ✅
{ index: true, Component: Home }
<!-- Use index for the default child route --> # ✅
ErrorBoundary: RootErrorBoundary
<!-- Define a root error boundary --> # ✅
❌ Don’t Do This:
import { RouterProvider } from "react-router";
<!-- Missing flushSync integration in browsers --> # ❌
# Forget the Outlet in the root component
<!-- Child routes never render --> # ❌
# Use the same component for the root and the index
<!-- Recursive rendering --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Child routes not rendering | Missing <Outlet /> | Add Outlet to root |
| Navigation not synchronous | Wrong RouterProvider import | Import from react-router/dom |
| Error crashes entire app | No ErrorBoundary | Add root boundary |
| 404 on subdirectory deploy | Missing basename | Set basename option |
| Index route not matching | No index: true | Add index property |
Real-World Examples
1. Create Router
const router = createBrowserRouter([{ path: "/", Component: Root }]);
2. Render Provider
<RouterProvider router={router} />
3. Root with Outlet
function Root() { return <div><Outlet /></div>; }
4. Index Route
{ index: true, Component: Home }
5. Nested Route
{ path: "about", Component: About }
6. Error Boundary
ErrorBoundary: RootErrorBoundary
7. Basename
createBrowserRouter(routes, { basename: "/app" })
8. Loader
loader: () => fetch("/api/data")
9. Action
action: ({ request }) => { ... }
10. Verify Import Path
import { RouterProvider } from "react-router/dom";
Visual
Router Setup Flow
┌──────────────────────────────────────────────┐
│ createBrowserRouter(routes) │
│ → Returns router object │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ <RouterProvider router={router} /> │
│ → Renders matching route │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Root component │
│ → Contains <Outlet /> for children │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Child component renders in <Outlet /> │
└──────────────────────────────────────────────┘
Import Paths
┌──────────────────────────────────────────────┐
│ react-router │
│ ├── createBrowserRouter │
│ ├── Outlet │
│ └── useRouteError │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ react-router/dom │
│ └── RouterProvider │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Router creation | createBrowserRouter(routes) |
| Router rendering | <RouterProvider router={router} /> |
RouterProvider import | react-router/dom |
createBrowserRouter import | react-router |
| Root layout | Component with <Outlet /> |
| Index route | { index: true } |
| Error boundary | ErrorBoundary: Component |
| Basename | Second argument option |
Key takeaways:
- The
RouterProvidermust come fromreact-router/dom. Thereact-routerexport omits thereact-domflushSyncintegration. In a browser, the DOM-specific import is the correct choice for synchronous navigation updates . - The root component must render
<Outlet />. TheOutletis the placeholder where the child routes render. Without it, the child components never appear . - The
index: trueproperty defines the default child route. It renders at the parent’s path when no other child matches. The index route cannot have children . - The
ErrorBoundarycatches errors from any child route. It provides a graceful fallback when a loader or a component throws. TheuseRouteErrorhook provides the error object . - The
basenameoption sets the base URL. It is required when the application is deployed to a subdirectory. The<Link to="/" />resolves to the basename when it is set .
Remember: createBrowserRouter creates the router. RouterProvider renders it. The Outlet is the placeholder. The index is the default. The ErrorBoundary is the fallback. The basename is the subdirectory. The import path is the trap. The react-router/dom is the correct path for the browser.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!