Part 7.1 — Routing Fundamentals with React Router
Routing is not just navigation.
It’s architecture.
Your routing structure determines:
how features are discovered
how layouts are shared
how state resets (or doesn’t)
how URLs communicate intent
In this post, you’ll learn how to use React Router to build a routing system that scales as your app grows—without hacks or accidental complexity.
1. Think of Routes as Features, Not Pages
A common mistake is treating routes like static pages.
Instead, think of routes as feature entry points.
Good routes:
reflect user intent
group related UI and data
map cleanly to backend REST resources
Bad routes are flat, repetitive, and tightly coupled to UI structure.
2. Install and Set Up React Router
Install:
pnpm add react-router-domAt the app root:
import { BrowserRouter } from 'react-router-dom';
function Root() {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
}React Router now owns navigation state.
3. Define Routes Declaratively
Avoid conditional routing logic scattered across components.
Use a central route definition:
import { Routes, Route } from 'react-router-dom';
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
);
}This makes navigation explicit and discoverable.
4. Layout Routes (The Most Important Concept)
Most real apps share layout across routes.
function AppLayout() {
return (
<>
<Header />
<main>
<Outlet />
</main>
</>
);
}Use it like this:
<Route element={<AppLayout />}>
<Route path="/" element={<Home />} />
<Route path="/users" element={<UsersPage />} />
</Route>Benefits:
shared navigation
consistent structure
no duplicated wrappers
Layout routes are the backbone of scalable routing.
5. Nested Routes = Nested Responsibility
Nested routes mirror feature structure.
<Route path="/users" element={<UsersLayout />}>
<Route index element={<UsersList />} />
<Route path=":id" element={<UserDetail />} />
</Route>This:
groups related routes
scopes layouts
makes ownership clear
Avoid deeply nested routes unless features truly nest.
6. Route Params Are Data, Not Strings
Access params with useParams:
const { id } = useParams<{ id: string }>();Convert early:
const userId = Number(id);Never pass raw string params deep into your app.
Type boundaries matter.
7. Index Routes for Default Views
Index routes define what renders at the parent path.
<Route path="/users">
<Route index element={<UsersList />} />
<Route path=":id" element={<UserDetail />} />
</Route>This avoids:
duplicate paths
conditional rendering hacks
Index routes keep intent clear.
8. Navigation: Declarative, Not Imperative
Prefer:
<Link to="/users">Users</Link>Over:
navigate('/users');Imperative navigation is for:
form submissions
redirects after actions
Declarative navigation is for:
menus
breadcrumbs
links
Use each where it belongs.
9. Programmatic Navigation (Use Sparingly)
const navigate = useNavigate();
navigate(`/users/${id}`);Common use cases:
after create/update/delete
after login/logout
error recovery flows
Don’t replace links with navigate calls.
10. Error Boundaries per Route
React Router supports route-level error boundaries.
<Route
path="/users"
element={<UsersPage />}
errorElement={<ErrorFallback />}
/>This prevents:
entire app crashes
cascading failures
Errors should fail locally, not globally.
11. Route-Driven Data Fetching (Conceptual)
Even without loaders, routes define when data should load.
Rules:
fetch data when route becomes active
clean up on unmount
don’t prefetch everything globally
Routes define lifecycle boundaries.
12. URLs Are Part of Your API
Treat URLs like contracts.
Good URLs:
are stable
describe resources
encode state meaningfully
Bad URLs:
reflect UI internals
change frequently
leak implementation details
If you wouldn’t expose it in a REST API, don’t encode it in a URL.
13. Common Routing Smells
Watch for:
deeply nested routes without clear ownership
route definitions scattered across files
conditional rendering instead of routes
global state used to track “current page”
These indicate routing responsibilities are misplaced.
14. Summary
You now know how to:
think of routes as architecture
define routes declaratively
use layout routes effectively
structure nested routes cleanly
handle params safely
separate navigation concerns
treat URLs as stable contracts
Related
Part 7.5 — Build Optimization with Vite: Code Splitting & Env Handling
Vite makes development fast by default. This post shows how to make production builds just as intentional—lean bundles, predictable envs, and code that ships only when needed.
Part 7.4 — Performance Hooks: useMemo, useCallback & useDeferredValue
Performance hooks are scalpels, not band-aids. This post teaches you how to use them intentionally—only where they solve real problems.
Part 7.3 — Custom Hooks as Architecture: Patterns & Pitfalls
Custom hooks aren’t just helpers. Used well, they define architectural seams in your React app. Used poorly, they hide complexity and make refactoring painful.
Comments