Dynamic Routing in Next.js
As part of this tutorial, you will learn how to set up dynamic routing in Next.js, including pre-rendering, server-side rendering, and static generation.
Pre RenderingAnchor
Next.js is a hybrid framework on top of React, which enables static _and _server-side rendering. It is packed with awesome features like file system routing, dynamic routing, image optimization, typescript support, code-splitting & bundling, etc., which are useful for any project. All in all, it is an impressive production framework for React.
When a page is loaded by the browser (client), its javascript code runs, making the page fully interactive. In addition to client-side rendering, Next.js offers pre-rendering for your views, which means that the HTML is generated in advance by Next.js, and it need not rely on the client-side Javascript.
Pre-rendering can happen in two ways
- Server-side
- Static Generation
Server-Side RenderingAnchor
When we cannot pre-render a page’s view ahead of a users request, i.e., in cases where we need to check the request and respond accordingly with a dynamically up-to-date page, we can either use server-side rendering or client-side rendering, as the data on the page depends on the request.
In server-side rendering, the client receives the request; the server then generates the HTML and sends it back to the client. So here, the HTML is generated by the server on each request.
Static GenerationAnchor
When the data on a page is static, i.e., it doesn’t matter who requests, the response will always be the same. For instance, for a blog post, it doesn’t matter who requests it; it would always deliver the same content.
Since we have to deliver the same page, we can pre-render it during build time, i.e., statically generate it. This will result in faster responses than client-side or server-side rendering as the HTML page is generated at the build time and not run time.
In the case of a blog application, our static frontend pages in a Next.js App can be structured as below.
/pages/post/post1.js/pages/post/post2.js/pages/post/post3.js
And when we run the next build
command, these pages will be statically generated once, and the same copy can be quickly served to the users every time.
Now let’s say that we want to add ten more blogs; one option to do so is to create additional static react components for every blog like post4.jsx
, post5.jsx
, post6.jsx,
and so on, and then build and deploy it. One caveat with this approach is that it will probably duplicate the same code repeatedly and create additional components. This is not a neat scalable solution. Imagine the code directory of a blog site with thousands of blogs built with this approach.
\
We need a way to dynamically generate any number of routes, and we need to do that at build time to serve statically pre-rendered pages. Next.js supports dynamic routing, which helps us achieve this use case. Let us try to learn more about dynamic routing with the help of an example.
ExampleAnchor
Use Case: We want to build a simple frontend blog application that will dynamically create any number of routes (example: post/1, post/post2, post/3, ... post/n). The number of routes can vary and can come from an external data source (eg: a database), The pages should be statically generated at build time, so we do not have to pre-render them on run time. Lastly, we want to support this with the help of a single react component and avoid duplicate code. We assume that you are comfortable with the basics of Next.js and React and that you have a Next.js app up and running.
If you are not familiar with Next.js, we recommend you go through some articles below and get started.
We want to display our blog on routes like /post/1, /post/2, etc. In Next.js, you can create a dynamic route by beginning the file name with [
and ending it with ]
; for our use case let us create one at pages/post/[id].js
any request in the browser like /post/1
, /post/2
, /post/a
will match to this page.
Assuming each of our posts will have three fields - title, description, and date, we can render this template.
// pages/post/[id].jsexport default function Post({ postData }) {return (<div className='bg-gray-800 h-screen p-16 text-gray-100'><div className='text-center font-bold text-3xl'>{postData.title}</div><div className='text-justify my-8 text-gray-200'>{postData.description}</div><div className="text-gray-400">Published On: {postData.date}</div></div>);}
Let us create two functions that will provide data to our application.
First add a function getPostIdList()
that will return the list of posts in the format below.
// lib/posts.jsexport async function getPostIdList() {return [{params: {id: '1'}}, {params: {id: '2'}}, {params: {id: '3'}}]}
Two important things to note here are:
- The response array should have the same format, as shown above. This will help next.js to generate paths for our routes dynamically. We used
'id'
as our inner key since we created our file as `[id].js`
If our dynamic route had been /pages/post/[postId].js
, we would use postId
as the key here.
- Next.js requires the value corresponding to the
id
key to be always a string.
Now, let's add another function getPostDetails()
that will return some dummy data for each post.
// lib/posts.jsexport async function getPostDetails(postId) {const dataSet = {'1': {title: 'Post 1',description: 'Lorem ipsum dolor sit amet...',date: 'Oct 10, 2022'},'2': {title: 'Post 2',description: 'Lorem ipsum dolor sit amet...',date: 'Oct 20, 2022'},'3': {title: 'Post 3',description: 'Lorem ipsum dolor sit amet...',date: 'Oct 30, 2022'}}return dataSet[postId]}
For demo purposes, we have populated the data in this file itself. In a real-world application, you would want to replace this dummy data with actual API calls to get data from your data source.
Now, let us use these helper functions to complete our dynamic route. To generate the dynamic routes, we can use a function provided by Next.js named getStaticPaths()
// pages/post/[id].jsimport { getPostDetails, getPostIdList } from '../../lib/posts';export async function getStaticPaths() {const paths = await getPostIdList();return {paths,fallback: false,};}
getStaticPaths()
gets the list of paths from getPostIdList()
and generates the respective paths.
In development getStaticPaths()
will run on every request. In production, it will only run at build time.
Finally implement getStaticProps()
as shown below
export async function getStaticProps({ params }) {const postData = await getPostDetails(params.id);return {props: {postData,},};}
getStaticProps()
gets the route params as an input, it will contain the id, and we use getPostDetails
that we created earlier to get details for a particular post.
And, here we have the final component:
// pages/post/[id].jsimport { getPostDetails, getPostIdList } from '../../lib/posts';export async function getStaticPaths() {const paths = await getPostIdList();return {paths,fallback: false,};}export async function getStaticProps({ params }) {const postData = await getPostDetails(params.id);return {props: {postData,},};}export default function Post({ postData }) {return (<div className='bg-gray-800 h-screen p-16 text-gray-100'><div className='text-center font-bold text-3xl'>{postData.title}</div><div className='text-justify my-8 text-gray-200'>{postData.description}</div><div className="text-gray-400">Published On: {postData.date}</div></div>);}