Dynamic segments and params
Each blog post has its own address: /posts/1, /posts/2 and so on. You cannot create a route per post, so part of the address is made dynamic. A dynamic segment starts with a colon, and its value goes into the params object.
A route with a param
Add a route to app/routes.ts inside the layout:
// app/routes.ts
route("posts/:postId", "routes/post.tsx"),And create app/routes/post.tsx:
// app/routes/post.tsx
import type { Route } from "./+types/post";
export default function Post({ params }: Route.ComponentProps) {
return <h1>Post number {params.postId}</h1>;
}Open http://localhost:5173/posts/7 — "Post number 7" appears. params.postId is a string, and TypeScript knows about it thanks to the generated types: a typo like params.id is flagged immediately.
Linking to the route
The link address is built from the param value:
// app/routes/home.tsx
import { Link } from "react-router";
export default function Home() {
const ids = [1, 2, 3];
return (
<ul>
{ids.map((id) => (
<li key={id}>
<Link to={`/posts/${id}`}>Post {id}</Link>
</li>
))}
</ul>
);
}The home page shows three links, each leading to its own post page.
Several params
An address can have several segments:
// app/routes.ts
route("authors/:authorId/posts/:postId", "routes/author-post.tsx"),For /authors/anna/posts/3, params contains authorId with "anna" and postId with "3".
Optional segment and splat
| Pattern | Matching addresses | Value |
|---|---|---|
:postId | /posts/5 | params.postId |
:lang? | /about and /en/about | params.lang or undefined |
* | /files/a/b.txt | params["*"], here a/b.txt |
A route("*", "routes/not-found.tsx") at the end of the list catches all unknown addresses — the 404 page.
The useParams hook
In a nested component that does not receive params as props, read them with the useParams() hook from react-router. In the route module itself the params prop is more convenient: it is precisely typed.
Common mistake
A param is always a string. params.postId === 7 is never true, and looking up a post by a numeric id finds nothing. Convert the value explicitly with Number(params.postId) and check the result, because anything can be typed into the address, for example /posts/abc.