React routing cheatsheet.

React Router 7

npm i react-router
import { BrowserRouter, Routes, Route, Link, useParams } from "react-router";

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/users/1">User 1</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/users/:id" element={<User />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

function User() {
  const { id } = useParams();
  return <p>User {id}</p>;
}

Nested routes

<Route path="/users" element={<UsersLayout />}>
  <Route index element={<UsersList />} />
  <Route path=":id" element={<UserDetail />} />
</Route>

<Outlet /> in UsersLayout renders matched child.

Programmatic nav

import { useNavigate } from "react-router";

const nav = useNavigate();
nav("/users/1");
nav(-1);              // back
nav("/x", { replace: true });

Search params

import { useSearchParams } from "react-router";

const [params, setParams] = useSearchParams();
const q = params.get("q");
setParams({ q: "hello" });

Loaders (data router)

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

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    loader: async () => fetchSession(),
    children: [
      {
        path: "users/:id",
        element: <User />,
        loader: async ({ params }) => fetchUser(params.id),
      },
    ],
  },
]);

<RouterProvider router={router} />

// In component:
const user = useLoaderData<typeof loader>();

Data is pre-fetched in parallel with code splitting.

Actions

{
  path: "users/:id",
  element: <UserEdit />,
  action: async ({ request, params }) => {
    const form = await request.formData();
    await updateUser(params.id, form);
    return redirect(`/users/${params.id}`);
  },
}

// Component:
<Form method="post">
  <input name="name" />
  <button>Save</button>
</Form>

TanStack Router (typed)

npm i @tanstack/react-router
import { Router, Route, RootRoute, Link } from "@tanstack/react-router";

const root = new RootRoute({ component: () => <Outlet /> });
const home = new Route({ getParentRoute: () => root, path: "/", component: Home });
const user = new Route({
  getParentRoute: () => root,
  path: "users/$id",
  loader: ({ params }) => fetchUser(params.id),
  component: User,
});

const tree = root.addChildren([home, user]);
const router = new Router({ routeTree: tree });

// In User component:
const { id } = userRoute.useParams();
const data = userRoute.useLoaderData();

Routes are fully typed: params, search, loader data — all checked.

File-based routing

npm i @tanstack/router-plugin
src/routes/
├── __root.tsx
├── index.tsx
├── users.tsx              # /users
└── users.$id.tsx          # /users/:id

Generates the tree at build time.

Search params (typed)

const route = new Route({
  validateSearch: z.object({ q: z.string().optional() }),
});

const { q } = route.useSearch();

Search params are validated and typed.

<Link to="/users/$id" params={{ id: "1" }} preload="intent">
  User 1
</Link>

Prefetches data on hover/focus.

Outlets

import { Outlet } from "react-router";    // or tanstack

function Layout() {
  return (
    <div>
      <Sidebar />
      <main><Outlet /></main>
    </div>
  );
}

Lazy routes

{
  path: "settings",
  lazy: async () => {
    const m = await import("./Settings");
    return { Component: m.default };
  },
}

Reduces initial bundle.

import { NavLink } from "react-router";

<NavLink to="/" className={({ isActive }) => isActive ? "current" : ""}>
  Home
</NavLink>

Protected routes

function RequireAuth({ children }: { children: React.ReactNode }) {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" />;
  return <>{children}</>;
}

<Route path="/dashboard" element={
  <RequireAuth><Dashboard /></RequireAuth>
} />

Or use a loader:

{
  path: "dashboard",
  loader: async () => {
    const user = await getUser();
    if (!user) throw redirect("/login");
    return user;
  },
}

Common mistakes

  • Using useEffect to fetch route data — use loaders.
  • Mixing <Switch> (v5) with v7 syntax — different paradigm.
  • Forgetting <Outlet /> in layout — children won’t show.
  • Path users/:id without leading slash — relative path matters.
  • Setting search params on every render — infinite loop.

Read this next

If you want my router + auth setup, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .