A Beginner's Guide to Dynamic Routing in Next.js
Last week, I went over how static routing works in Next.js. If you missed it, you can read it there: Static Routing in Next.js.
This week, the topic is dynamic routing in Next.js. More precisely, we will go over:
- How to create dynamic routes
- How to navigate between pages with the Link component
- How to retrieve the query parameters (like ids)
Extra: How to navigate between pages with the useRouter hook or the HOC withRouter
Want to become a Next.js pro?
I am putting together an online course on Next.js. If you are interested, sign up and I will keep you updated.
How to create dynamic routes in Next.js
As mentioned in the tutorial on static routing, Next.js defines routes based on the concept of pages
.
What does that mean? Every Next.js project comes with a pages
folder. The structure of the pages folder determines the structure of your routes and every file inside that folder maps to a route in your application.
Essentially, every time you want to create a route, you need to add a file in the pages folder. Keep in mind that the pages folder itself represents your root url (meaning /).
For static routing, you can create a new route by adding a index.js or a named file like about.js.
pages/index.js maps to /
pages/about.js maps to /about
Tip: For more info on how to create static routes (including nested routes), read my tutorial on static routing in Next.js.
But how does it work for dynamic routes? Say I wanted to create a blog, how would I add a route such as myblog.com/posts/:id?
Next.js handles dynamic routes by supporting brackets around parameters (e.g [id]) as a filename. Going back to my blog example, I would therefore create a [id].js
file inside my posts folder.
As a result, /pages/posts/[id].js would map to /posts/[id] where id is the unique id of your post.
Dynamic Nested Routes in Next.js
Can I create dynamic nested routes? Say I wanted a page for comments related to a particular post, could I have a url such as /posts/[id]/[commentId]?
The answer is Yes!
For nested routes, you have to create a folder instead of a file. The syntax stays the same meaning that your folder would be called [id]
. You can then add new routes inside. Here is the end result:
pages/
│ index.js -> url: /
│
└───posts/
| index.js -> url: /posts
|
└─── [id]/
index.js -> url: /posts/[id]
commentId.js -> url: /posts/[id]/[commentId]
Now that all our routes are set up, let's explore how to navigate between the different pages.
How to navigate to dynamic routes in Next.js
Next.js offers a component called Link that allows for navigation between pages. This component accepts an href and wraps around a piece of code (say an anchor) to navigate to a page. Let's try it.
Try #1:
import Link from "next/link";
export default function Home() {
return (
<div>
<h1>Welcome to my blog</h1>
<div>
<Link href="/posts/1">
<a>Post #1</a>
</Link>
</div>
</div>
);
}
Straightforward, but hard-coded links are not very practical. I am going to create a separate posts object and use that to create my url.
Try #2:
import Link from "next/link";
const posts = [
{
id: 1,
title: "Post #1"
},
{
id: 2,
title: "Post #2"
}
];
export default function Home() {
return (
<div>
<h1>Welcome to my blog</h1>
{posts.map((post) => (
<div key={`post-${post.id}`}>
<Link href={`/posts/${encodeURIComponent(post.id)}`}>
<a>{post.title}</a>
</Link>
</div>
))}
</div>
);
}
Better! But what if I update my route later? I will have to go through all my code and update all the links.
Try #3:
import Link from "next/link";
const ROUTE_POST_ID = "posts/[id]";
const posts = [
{
id: 1,
title: "Post #1"
},
{
id: 2,
title: "Post #2"
}
];
export default function Home() {
return (
<div>
<h1>Welcome to my blog</h1>
{posts.map((post) => (
<div key={`post-${post.id}`}>
<Link
href={{
pathname: ROUTE_POST_ID,
query: { id: post.id }
}}
>
<a>{post.title}</a>
</Link>
</div>
))}
</div>
);
}
What changed? Instead of an hard-coded url, the Link component can also accept an object for href.
This object contains two parameters pathname
and query
. The pathname is the route we want to navigate to (in our particular case, /posts/[id]) and the query which will contain all the data necessary for our dynamic route (like id).
The Link component takes those two and automatically formats it into the right url. That's much better!
Access query parameters in your page
Can I access the parameters in my new page? In other words, when I get to /posts/[id], can I get the id part?
You can get all the information you need and more from the router itself. Simply, import useRouter and get the router object. The same way, we pass a query object for navigating query : { id: post.id }
, we can retrieve it in our new page.
import { useRouter } from "next/router";
export default function PostPage() {
const router = useRouter();
return <div>Post #{router.query.id}</div>;
}
Here is the end result:
Extra: Access your Next.js router and navigate with the useRouter hook
Say you want to navigate to a new page other than through a link, for example by clicking a button, you can do this with the router. Remember the useRouter
we used to get our query parameters? You can also use it to push a new page (or go back).
import { useRouter } from "next/router";
const ROUTE_POST_ID = "posts/[id]";
const posts = [
{
id: 1,
title: "Post #1"
},
{
id: 2,
title: "Post #2"
}
];
export default function Home() {
const router = useRouter();
const navigate = (id) =>
router.push({
pathname: ROUTE_POST_ID,
query: { id }
});
return (
<div>
<h1>Welcome to my blog</h1>
{posts.map((post) => (
<div key={`post-${post.id}`}>
<button onClick={() => navigate(post.id)}>{post.title}</button>
</div>
))}
</div>
);
}
Extra: Access your Next.js router and navigate with the HOC withRouter
Unfortunately, hooks don't work with class components (meaning components that extends React.Component). That's where the withRouter HOC comes in handy.
It will wrap around your class component and allow you to access the router through its props.
import React from "react";
import { withRouter } from "next/router";
const ROUTE_POST_ID = "posts/[id]";
const posts = [
{
id: 1,
title: "Post #1"
},
{
id: 2,
title: "Post #2"
}
];
class Home extends React.Component {
navigate = (id) =>
this.props.router.push({
pathname: ROUTE_POST_ID,
query: { id }
});
render() {
return (
<div>
<h1>Welcome to my blog</h1>
{posts.map((post) => (
<div key={`post-${post.id}`}>
<button onClick={() => this.navigate(post.id)}>{post.title}</button>
</div>
))}
</div>
);
}
}
export default withRouter(Home);
There you go! I created a sandbox if you would like to see the complete code and play around with it a bit: Next.js Dynamic Routing CodeSandbox.
Stay tuned for more Next.js tutorials!
In the meantime, if you have missed my tutorial on static routing in Next.js or on how to deploy your Next.js application to Heroku, don't forget to check them out!