Explorer
React

Routing – React Router

Yes. If you're writing this as a current React Router blog, I would update the previous content to use the Data Router approach with createBrowserRouter + RouterProvider.

The current React Router docs show createBrowserRouter() with route objects, and RouterProvider renders the router. The current installation docs also use react-router rather than the older react-router-dom-centric setup. (React Router)

React Router with createBrowserRouter

React applications often contain multiple pages:

TEXT
/
├── /products
├── /products/:productId
├── /cart
├── /login
└── /dashboard

Instead of loading a completely new HTML page for every URL, React Router allows us to manage navigation on the client side.

The modern approach is to create a router using:

JS
createBrowserRouter()

and provide it to React using:

JSX
<RouterProvider />

The basic flow is:

TEXT
URL
 ↓
createBrowserRouter
 ↓
Match Route
 ↓
Load Route Data
 ↓
Render Component

1. Installing React Router

For a new application using the current React Router Data mode:

BASH
npm install react-router

The official installation documentation currently uses react-router. (React Router)

Then we can import:

JS
import {
  createBrowserRouter,
} from "react-router";

import {
  RouterProvider,
} from "react-router/dom";

For browser applications, the current documentation recommends RouterProvider from react-router/dom, which wires up the React DOM flushSync implementation automatically. (React Router)


2. What is createBrowserRouter?

createBrowserRouter() creates a data router for the application.

For example:

JS
const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },
]);

The argument is an array of route objects.

A route object describes things such as:

TEXT
path
Component
children
loader
action
errorElement
lazy

and more. (React Router)

So instead of defining routes like this:

JSX
<Routes>
  <Route path="/" element={<Home />} />
</Routes>

we can define them as JavaScript objects:

JS
const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },
]);

3. RouterProvider

After creating the router, we need to give it to React.

JSX
<RouterProvider router={router} />

For example:

JSX
import ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
} from "react-router";

import {
  RouterProvider,
} from "react-router/dom";

const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },
]);

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <RouterProvider router={router} />
);

RouterProvider renders the UI for the router and should generally be placed near the top of the application tree. The router itself should be created outside the React render tree rather than recreated during renders. (React Router)


4. Basic Routing

Let's create a few components:

JSX
const Home = () => {
  return <h1>Home Page</h1>;
};

const Products = () => {
  return <h1>Products Page</h1>;
};

const Cart = () => {
  return <h1>Cart Page</h1>;
};

Now define our routes:

JS
const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },
  {
    path: "/products",
    Component: Products,
  },
  {
    path: "/cart",
    Component: Cart,
  },
]);

Now React Router knows:

TEXT
/          → Home
/products  → Products
/cart      → Cart

The routing flow becomes:

TEXT
User visits /products
        ↓
Router matches /products
        ↓
Products Component
        ↓
React renders Products UI

5. Route Objects

The objects passed to createBrowserRouter() are called Route Objects.

For example:

JS
{
  path: "/products",
  Component: Products,
}

A route object can contain much more than just a path and component.

For example:

JS
{
  path: "/products/:id",
  Component: ProductDetails,
  loader: productLoader,
  errorElement: <ErrorPage />,
}

This is one of the major advantages of the Data Router approach.

The route configuration can contain:

TEXT
Route
 ├── path
 ├── Component
 ├── loader
 ├── action
 ├── errorElement
 ├── children
 └── lazy

React Router's current Route Object documentation describes route objects as the foundation for data loading, actions, revalidation, error boundaries, lazy loading, and other route behavior. (React Router)


6. Link

For navigation between routes, we can use Link.

JSX
import { Link } from "react-router";

const Navbar = () => {
  return (
    <nav>
      <Link to="/">Home</Link>

      <Link to="/products">
        Products
      </Link>

      <Link to="/cart">
        Cart
      </Link>
    </nav>
  );
};

The important difference is that we don't need to manually manipulate:

JS
window.location

React Router handles the client-side navigation.

Conceptually:

TEXT
Click Link
   ↓
Router navigation
   ↓
URL changes
   ↓
Route matching
   ↓
New component renders

7. Link vs <a>

For internal React Router navigation:

JSX
<Link to="/products">
  Products
</Link>

is generally preferred over:

JSX
<a href="/products">
  Products
</a>

The Link component participates in React Router's client-side navigation system.


8. Dynamic Routes

A very common requirement is displaying a product based on its ID.

For example:

TEXT
/products/1
/products/2
/products/100

We don't want to create:

TEXT
/products/1
/products/2
/products/3
...

Instead, we define a dynamic segment:

JS
const router = createBrowserRouter([
  {
    path: "/products/:productId",
    Component: ProductDetails,
  },
]);

The :productId part is a dynamic segment.

React Router parses dynamic segments and makes them available to the route through routing APIs. (React Router)


9. useParams

Inside the component, we can retrieve the parameter using useParams.

JSX
import { useParams } from "react-router";

const ProductDetails = () => {
  const { productId } = useParams();

  return (
    <h1>
      Product ID: {productId}
    </h1>
  );
};

For:

TEXT
/products/101

we get:

JS
productId = "101"

For:

TEXT
/products/500

we get:

JS
productId = "500"

So:

TEXT
/products/:productId
       ↓
useParams()
       ↓
productId

10. Nested Routes

Nested routing is extremely useful for dashboard applications.

Suppose we have:

TEXT
/dashboard
/dashboard/profile
/dashboard/settings
/dashboard/orders

We can configure:

JS
const router = createBrowserRouter([
  {
    path: "/dashboard",
    Component: Dashboard,
    children: [
      {
        index: true,
        Component: DashboardHome,
      },
      {
        path: "profile",
        Component: Profile,
      },
      {
        path: "settings",
        Component: Settings,
      },
      {
        path: "orders",
        Component: Orders,
      },
    ],
  },
]);

Notice that the child paths don't start with /.

We write:

JS
path: "profile"

rather than:

JS
path: "/dashboard/profile"

React Router combines the parent and child paths.

So:

TEXT
/dashboard
      +
profile
      ↓
/dashboard/profile

Nested routes are configured through the children property. (React Router)


11. Outlet

Now we need to decide where the child route should render.

That's where Outlet comes in.

JSX
import { Outlet } from "react-router";

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>

      <nav>
        <Link to="profile">
          Profile
        </Link>

        <Link to="settings">
          Settings
        </Link>
      </nav>

      <Outlet />
    </div>
  );
};

Suppose the URL is:

TEXT
/dashboard/profile

The rendering becomes:

TEXT
Dashboard
   │
   ├── Dashboard UI
   │
   └── <Outlet />
          │
          ↓
       Profile

If the URL changes to:

TEXT
/dashboard/settings

then:

TEXT
Dashboard
   │
   ├── Dashboard UI
   │
   └── <Outlet />
          │
          ↓
       Settings

React Router renders child routes through the parent's <Outlet />. (React Router)


12. Index Routes

An index route is the default child route.

For example:

JS
{
  path: "/dashboard",
  Component: Dashboard,
  children: [
    {
      index: true,
      Component: DashboardHome,
    },
    {
      path: "settings",
      Component: Settings,
    },
  ],
}

Now:

TEXT
/dashboard
      ↓
DashboardHome


/dashboard/settings
      ↓
Settings

The important part is:

JS
index: true

An index route renders into the parent's Outlet at the parent's URL. (React Router)


13. Programmatic Navigation

Sometimes navigation happens after some logic.

For example:

TEXT
Login
 ↓
API request
 ↓
Login successful
 ↓
Dashboard

We can use:

JS
useNavigate()

Example:

JSX
import { useNavigate } from "react-router";

const Login = () => {
  const navigate = useNavigate();

  const handleLogin = async () => {
    // login API

    navigate("/dashboard");
  };

  return (
    <button
      Login
    </button>
  );
};

The flow is:

TEXT
Login Button
     ↓
API Request
     ↓
Success
     ↓
navigate("/dashboard")
     ↓
Dashboard

14. Navigate Back

useNavigate() can also work with browser history.

JS
navigate(-1);

means:

TEXT
Go back

For example:

JSX
<button => navigate(-1)}>
  Back
</button>

We can also use:

JS
navigate(1);

to move forward in the browser history.


15. Protected Routes

Authentication is a common React Router requirement.

Suppose:

TEXT
/dashboard

should only be accessible to authenticated users.

With the Data Router approach, we can use route-level loaders and redirects for authentication.

For example:

JS
import {
  redirect,
} from "react-router";

const dashboardLoader = async () => {
  const isAuthenticated = checkAuth();

  if (!isAuthenticated) {
    throw redirect("/login");
  }

  return null;
};

Then:

JS
const router = createBrowserRouter([
  {
    path: "/dashboard",
    Component: Dashboard,
    loader: dashboardLoader,
  },
]);

The flow becomes:

TEXT
User visits /dashboard
        ↓
dashboardLoader
        ↓
Authenticated?
     /       \
   Yes        No
    ↓          ↓
Dashboard   redirect
               ↓
            /login

This is one of the areas where the Data Router approach becomes particularly powerful: route configuration can contain data-loading and navigation behavior rather than putting all routing logic inside components. (React Router)


16. Route Loaders

One of the biggest advantages of createBrowserRouter is the loader.

Suppose we want to load a product before rendering its page.

We can write:

JS
const productLoader = async ({ params }) => {
  const response = await fetch(
    `/api/products/${params.productId}`
  );

  return response.json();
};

Then:

JS
const router = createBrowserRouter([
  {
    path: "/products/:productId",
    Component: ProductDetails,
    loader: productLoader,
  },
]);

Now the route itself knows:

TEXT
Route
 ├── URL
 ├── Component
 └── Data loader

17. useLoaderData

Inside the component:

JSX
import { useLoaderData } from "react-router";

const ProductDetails = () => {
  const product = useLoaderData();

  return (
    <div>
      <h1>{product.name}</h1>
      <p>${product.price}</p>
    </div>
  );
};

The flow becomes:

TEXT
Navigate to /products/10
          ↓
Route matches
          ↓
loader()
          ↓
Fetch product
          ↓
Loader data
          ↓
ProductDetails
          ↓
useLoaderData()
          ↓
Render product

React Router's Data Router APIs allow loaders to load data associated with a route. (React Router)


18. Why createBrowserRouter Is Powerful

The older mental model was mostly:

TEXT
URL
 ↓
Route
 ↓
Component

With createBrowserRouter, a route can describe much more:

TEXT
                   Route
                     │
        ┌────────────┼────────────┐
        ↓            ↓            ↓
      URL         Component     Loader
                                  │
                                  ↓
                               Data

And it can additionally support things like:

TEXT
actions
error boundaries
lazy loading
revalidation
middleware

depending on the routing setup. (React Router)


19. Complete Modern Example

Here's a good starting structure for a blog or interview project:

JSX
import ReactDOM from "react-dom/client";

import {
  createBrowserRouter,
} from "react-router";

import {
  RouterProvider,
} from "react-router/dom";

import {
  Link,
  Outlet,
  useParams,
  useNavigate,
} from "react-router";

const Home = () => {
  return (
    <div>
      <h1>Home</h1>

      <Link to="/products">
        Products
      </Link>
    </div>
  );
};

const Products = () => {
  return <h1>Products</h1>;
};

const ProductDetails = () => {
  const { productId } = useParams();

  return (
    <h1>
      Product: {productId}
    </h1>
  );
};

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>

      <Link to="profile">
        Profile
      </Link>

      <Link to="settings">
        Settings
      </Link>

      <Outlet />
    </div>
  );
};

const Profile = () => {
  return <h2>Profile</h2>;
};

const Settings = () => {
  return <h2>Settings</h2>;
};

const NotFound = () => {
  return <h1>404 - Page Not Found</h1>;
};

const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },

  {
    path: "/products",
    Component: Products,
  },

  {
    path: "/products/:productId",
    Component: ProductDetails,
  },

  {
    path: "/dashboard",
    Component: Dashboard,
    children: [
      {
        index: true,
        Component: Profile,
      },
      {
        path: "profile",
        Component: Profile,
      },
      {
        path: "settings",
        Component: Settings,
      },
    ],
  },

  {
    path: "*",
    Component: NotFound,
  },
]);

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <RouterProvider router={router} />
);

20. Modern React Router Architecture

The overall architecture is now:

TEXT
                    createBrowserRouter()
                             │
                             ↓
                       Route Objects
                             │
             ┌───────────────┼───────────────┐
             ↓               ↓               ↓
           path          Component         loader
             │               │               │
             ↓               ↓               ↓
          URL Match        UI             API/Data
             │
             ↓
        RouterProvider
             │
             ↓
          React App

For nested routes:

TEXT
/dashboard
     │
     ↓
Dashboard
     │
     └── <Outlet />
            │
            ├── Profile
            └── Settings

21. createBrowserRouter vs BrowserRouter

This is an important interview question.

Traditional declarative approach

JSX
<BrowserRouter>
  <Routes>
    <Route
      path="/"
      element={<Home />}
    />
  </Routes>
</BrowserRouter>

Data Router approach

JS
const router = createBrowserRouter([
  {
    path: "/",
    Component: Home,
  },
]);

Then:

JSX
<RouterProvider router={router} />

The second approach gives you access to the Data Router features such as:

TEXT
loaders
actions
route-level errors
revalidation
lazy route modules

React Router's docs distinguish Declarative Mode from Data Mode, with createBrowserRouter being the browser runtime API for Data Mode. (React Router)


22. element vs Component

You will see both of these:

JS
{
  path: "/",
  element: <Home />,
}

and:

JS
{
  path: "/",
  Component: Home,
}

Both are supported in route configuration. The current Route Object API documents Component and element as alternatives. (React Router)

For a current createBrowserRouter setup, I would generally write:

JS
{
  path: "/",
  Component: Home,
}

because it is clean and aligns with the current documentation examples.


23. One More Option: JSX Route Definitions

If you prefer the older JSX-style syntax but still want a Data Router, React Router provides:

JS
createRoutesFromElements()

For example:

JSX
const routes = createRoutesFromElements(
  <>
    <Route
      path="/"
      Component={Home}
    />

    <Route
      path="/products"
      Component={Products}
    />
  </>
);

const router = createBrowserRouter(routes);

Then:

JSX
<RouterProvider router={router} />

React Router explicitly supports converting JSX <Route> elements into route objects with createRoutesFromElements(). (React Router)

But if you're learning the modern createBrowserRouter approach, I recommend learning the route-object format first.


Final Mental Model

For your blog, I would summarize the modern React Router architecture like this:

TEXT
                createBrowserRouter()
                         ↓
                  Route Objects
                         ↓
          ┌──────────────┼──────────────┐
          ↓              ↓              ↓
        path         Component        loader
          ↓              ↓              ↓
      URL Match          UI            Data
          │
          ↓
                  RouterProvider
                         ↓
                    React renders

And the most important APIs to learn are:

TEXT
createBrowserRouter
        ↓
RouterProvider
        ↓
Route Objects
        ↓
Component
        ↓
children
        ↓
Outlet
        ↓
Link / NavLink
        ↓
useNavigate
        ↓
useParams
        ↓
useSearchParams
        ↓
loader / useLoaderData
        ↓
action / Form

One correction to the previous article: if you're targeting the latest React Router APIs, don't center the blog around <BrowserRouter> + <Routes> + <Route> as the primary architecture. That's still a valid declarative mode, but createBrowserRouter() + RouterProvider is the better modern starting point when you want React Router's Data APIs. (React Router)

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.