React Router 03 ⚛️ Route Mapping — Matching paths to elements
The previous chapter created the router and attached it to the React tree with RouterProvider. This chapter covers the route objects that the router uses to decide which component renders for a given URL. A route object is a configuration record: it has a path, a component (or element), and optionally children, a loader, an action, and an error boundary. The router compares the current URL against the route objects, matches the most specific one, and renders the corresponding component inside the parent’s <Outlet />. The LFCS exam does not test React Router, but front-end engineering interviews and certification paths do. The critical distinction is between static paths (/about), dynamic segments (/users/:id), splats (/files/*), and optional segments (/posts/:year/:month?). The router’s matching algorithm is not a simple string comparison; it uses a ranking system that prefers static segments over dynamic segments and exact matches over splats.
Key point: A route object has a path and either a Component or an element. The path is a pattern, not a URL. The dynamic segment (:id) matches a single URL segment. The splat (*) matches the remainder of the URL. The optional segment (?) allows the segment to be absent. The router ranks the routes by specificity: static segments first, dynamic segments second, splats last. The most specific match wins.
Why route mapping requires a specific mental model
The specificity problem. A URL can match multiple route objects. The /users/new URL matches both /users/new (static) and /users/:id (dynamic). The router must choose one. The rule is that static segments are ranked higher than dynamic segments. The /users/new route is chosen when the URL is exactly /users/new. The /users/:id route is chosen when the URL is /users/123. This is the ranking system that the router uses to resolve ambiguity .
The dynamic segment problem. The :id segment matches any single URL segment. The /users/:id route matches /users/1, /users/abc, and /users/anything. The matched value is available in the useParams hook as params.id. The dynamic segment does not match across slashes: /users/1/extra does not match /users/:id; it requires a nested route or a splat.
The splat problem. The * segment matches the remainder of the URL, including slashes. The /files/* route matches /files/a, /files/a/b, and /files/a/b/c. The matched value is available as params["*"]. The splat is the mechanism for catching all unmatched paths under a prefix, such as a 404 page or a catch-all route.
The optional segment problem. The ? suffix makes a segment optional. The /posts/:year/:month? route matches both /posts/2026 and /posts/2026/10. The optional segment is useful for URLs that have a natural default when the segment is absent.
The pathless route problem. A route object without a path does not add a segment to the URL. It is used for layout routes that wrap a group of routes without changing the URL. The layout route renders its component, and the <Outlet /> renders the child routes. The pathless route is the mechanism for adding a shared layout, a provider, or an error boundary without affecting the URL structure .
The trade-off. The route mapping is declarative: the route array is the source of truth, and the router does the matching. The alternative is the manual parsing of window.location.pathname with a switch statement, which is what React Router replaces. The router’s ranking is more sophisticated than a string comparison, but it is also more predictable once the rules are understood.
a. Static, dynamic, and splat segments
The route object has a path string. The string is the pattern, not the literal URL.
const router = createBrowserRouter([
{
path: "/",
Component: Root,
children: [
{ index: true, Component: Home },
{ path: "about", Component: About },
{ path: "users/:id", Component: UserProfile },
{ path: "files/*", Component: FileBrowser },
{ path: "*", Component: NotFound },
],
},
]);
The about route matches the /about URL. The users/:id route matches /users/1, /users/abc, and any other single segment. The files/* route matches /files/, /files/a, /files/a/b, and any other path under /files/. The * route is the catch-all that matches any URL that the other routes do not match.
The path in the route object is relative when nested. The users/:id route is nested under the / root, so the full URL is /users/:id. The path: "users/:id" does not have a leading slash because the parent provides it.
b. Optional segments and pathless routes
The ? suffix makes a segment optional. The /posts/:year/:month? route matches both /posts/2026 and /posts/2026/10.
{
path: "posts/:year/:month?",
Component: PostsArchive,
}
The useParams hook returns { year: "2026", month: undefined } for /posts/2026, and { year: "2026", month: "10" } for /posts/2026/10. The component must handle the undefined case.
The pathless route has no path property. It is used for layout routes that render a shared component without adding a URL segment.
{
Component: DashboardLayout,
children: [
{ path: "dashboard", Component: Dashboard },
{ path: "settings", Component: Settings },
],
}
The DashboardLayout component renders around the Dashboard and Settings components. The URL for the dashboard is /dashboard, not /dashboard-layout/dashboard. The layout adds the shared structure (navigation, header) without changing the URL.
c. The route ranking and the matching algorithm
The router ranks the routes by specificity. The ranking is based on the segments:
- Static segments are ranked higher than dynamic segments.
- Dynamic segments are ranked higher than splats.
- Splats are ranked lower than everything else.
The ranking is applied at each level of the route tree. The /users/new route is chosen over /users/:id when the URL is /users/new, because the new segment is static and the :id segment is dynamic. The /users/:id route is chosen when the URL is /users/123, because the new route does not match 123.
The useParams hook returns the matched dynamic segments. The useLocation hook returns the current URL. The useMatch hook returns the match object for a specific path, which is useful for checking whether a route is active.
import { useParams, useLocation, useMatch } from "react-router";
function UserProfile() {
const { id } = useParams();
const location = useLocation();
const match = useMatch("/users/:id");
return (
<div>
<h1>User {id}</h1>
<p>Path: {location.pathname}</p>
<p>Matched: {match ? "yes" : "no"}</p>
</div>
);
}
The useParams returns the dynamic segments. The useLocation returns the pathname, search, and hash. The useMatch returns the match object if the current URL matches the pattern.
Complete Example Session
# ============================================
# PART 1: DEFINE STATIC ROUTES
# ============================================
const routes = [
{ path: "/", Component: Root, children: [
{ index: true, Component: Home },
{ path: "about", Component: About },
]},
];
# ============================================
# PART 2: ADD DYNAMIC SEGMENT
# ============================================
{ path: "users/:id", Component: UserProfile }
# ============================================
# PART 3: ACCESS THE PARAM
# ============================================
import { useParams } from "react-router";
function UserProfile() {
const { id } = useParams();
return <h1>User {id}</h1>;
}
# ============================================
# PART 4: ADD SPLAT
# ============================================
{ path: "files/*", Component: FileBrowser }
# ============================================
# PART 5: ACCESS THE SPLAT
# ============================================
function FileBrowser() {
const { "*": path } = useParams();
return <p>Path: {path}</p>;
}
# ============================================
# PART 6: ADD OPTIONAL SEGMENT
# ============================================
{ path: "posts/:year/:month?", Component: PostsArchive }
# ============================================
# PART 7: ADD PATHLESS LAYOUT
# ============================================
{
Component: DashboardLayout,
children: [
{ path: "dashboard", Component: Dashboard },
{ path: "settings", Component: Settings },
],
}
# ============================================
# PART 8: ADD CATCH-ALL
# ============================================
{ path: "*", Component: NotFound }
# ============================================
# PART 9: VERIFY ROUTES
# ============================================
# / → Home
# /about → About
# /users/123 → UserProfile with id=123
# /files/a/b → FileBrowser with path="a/b"
# /posts/2026 → PostsArchive with year=2026, month=undefined
# /unknown → NotFound
The nine parts trace the route mapping: static routes, dynamic segment, param access, splat, splat access, optional segment, pathless layout, catch-all, and verification.
Quick Reference
Path Patterns
| Pattern | Matches |
|---|---|
/about | Static path |
/users/:id | Dynamic segment |
/files/* | Splat |
/posts/:year/:month? | Optional segment |
* | Catch-all |
| (no path) | Pathless layout |
Hooks
| Hook | Returns |
|---|---|
useParams | Dynamic segments |
useLocation | Pathname, search, hash |
useMatch | Match object for a pattern |
useNavigate | Navigation function |
Route Ranking
| Rank | Pattern |
|---|---|
| 1 (highest) | Static segment |
| 2 | Dynamic segment |
| 3 | Splat |
Route Properties
| Property | Purpose |
|---|---|
path | URL pattern |
Component | React component |
element | JSX element |
children | Nested routes |
index | Default child route |
loader | Data loading function |
ErrorBoundary | Error boundary |
Best Practices
✅ Do This:
{ path: "users/:id", Component: UserProfile }
<!-- Use dynamic segment for variable IDs --> # ✅
{ path: "*", Component: NotFound }
<!-- Add a catch-all for unmatched routes --> # ✅
const { id } = useParams();
<!-- Access params with useParams --> # ✅
❌ Don’t Do This:
# Forget the leading slash in nested routes
<!-- Nested paths are relative to the parent --> # ❌
# Use the same path for multiple routes
<!-- The first match wins; the second is unreachable --> # ❌
# Assume the order in the array matters
<!-- The router ranks by specificity --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Dynamic segment not matching | Trailing slash or extra segment | Check the URL structure |
| Param undefined | useParams outside the route | Check the component tree |
| Catch-all matching everything | Placed before specific routes | Place last or use ranking |
| Optional segment not working | Wrong ? placement | Place ? after the segment |
| Pathless layout not rendering | No <Outlet /> in layout | Add <Outlet /> |
Real-World Examples
1. Static Route
{ path: "about", Component: About }
2. Dynamic Route
{ path: "users/:id", Component: UserProfile }
3. Splat
{ path: "files/*", Component: FileBrowser }
4. Optional Segment
{ path: "posts/:year/:month?", Component: PostsArchive }
5. Catch-All
{ path: "*", Component: NotFound }
6. Access Params
const { id } = useParams();
7. Access Splat
const { "*": path } = useParams();
8. Match Check
const match = useMatch("/users/:id");
9. Pathless Layout
{ Component: Layout, children: [...] }
10. Index Route
{ index: true, Component: Home }
Visual
Route Matching
┌──────────────────────────────────────────────┐
│ URL: /users/new │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Candidate routes: │
│ ├── /users/new (static) │
│ └── /users/:id (dynamic) │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Static wins. /users/new renders. │
└──────────────────────────────────────────────┘
Segment Types
┌──────────────────────────────────────────────┐
│ :id → Single segment │
│ * → Remainder (including slashes) │
│ ? → Optional segment │
│ (none) → Pathless layout │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Static path | /about |
| Dynamic segment | :id |
| Splat | * |
| Optional segment | :month? |
| Catch-all | * (at the end) |
| Pathless route | No path |
| Param access | useParams |
| Location access | useLocation |
| Match check | useMatch |
| Ranking | Static > dynamic > splat |
Key takeaways:
- The
pathis a pattern, not a URL. The router matches the current URL against the patterns and renders the most specific match. The static segments are ranked higher than the dynamic segments, and the dynamic segments are ranked higher than the splats . - The dynamic segment matches a single segment. The
:idmatches/users/1, but not/users/1/extra. The splat (*) is the mechanism for matching the remainder of the URL. - The optional segment is the
?suffix. The:month?allows the segment to be absent. The component must handle theundefinedcase. - The pathless route is the layout mechanism. It renders a component without adding a URL segment. The child routes render in the parent’s
<Outlet />. - The catch-all is the fallback. The
*route matches any URL that the other routes do not match. It is the 404 page. The router’s ranking ensures that it does not shadow the specific routes.
Remember: The route object is the configuration. The path is the pattern. The Component is the destination. The useParams is the access. The Outlet is the placeholder. The ranking is the resolution. The exam tests the path patterns and the access hooks.
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!