NextJS - Dynamic Routes I

NextJS - Dynamic Routes I



Information drawn from

We’ve populated the index page with the blog data, but we still haven’t created individual blog pages yet (here’s the desired result). We want the URL for these pages to depend on the blog data, which means we need to use dynamic routes.

In this lesson, you’ll learn:

Page Path Depends on External Data

In the previous lesson, we covered the case where the page content depends on external data. We used getStaticProps to fetch required data to render the index page.

In this lesson, we’ll talk about the case where each page path depends on external data. Next.js allows you to statically generate pages with paths that depend on external data. This enables dynamic URLs in Next.js.

nextjs-page-path-external-data.png Page Path Depends on External Data

How to Statically Generate Pages with Dynamic Routes

In our case, we want to create dynamic routes for blog posts:

Overview of the Steps

We can do this by taking the following steps. You don’t have to make these changes yet — we’ll do it all on the next page.

First, we’ll create a page called [id].js under pages/posts. Pages that begin with [ and end with ] are dynamic routes in Next.js.

In pages/posts/[id].js, we’ll write code that will render a post page — just like other pages we’ve created.

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

Now, here’s what’s new: We’ll export an async function called getStaticPaths from this page. In this function, we need to return a list of possible values for id.

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

Finally, we need to implement getStaticProps again - this time, to fetch necessary data for the blog post with a given id. getStaticProps is given params, which contains id (because the file name is [id].js).

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}

Implement getStaticPaths

First, let’s set up the files:

Then, open pages/posts/[id].js in your editor and paste the following code. We’ll fill in … later:

import Layout from '../../components/layout'

export default function Post() {
  return <Layout>...</Layout>
}

Then, open lib/posts.js and add the following getAllPostIds function at the bottom. It will return the list of file names (excluding .md) in the posts directory:

export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory)

  // Returns an array that looks like this:
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]
  return fileNames.map(fileName => {
    return {
      params: {
        id: fileName.replace(/\.md$/, '')
      }
    }
  })
}

Important: The returned list is not just an array of strings — it must be an array of objects that look like the comment above. Each object must have the params key and contain an object with the id key (because we’re using [id] in the file name). Otherwise, getStaticPaths will fail.

Finally, we’ll import the getAllPostIds function and use it inside getStaticPaths. Open pages/posts/[id].js and copy the following code above the exported Post component:

import { getAllPostIds } from '../../lib/posts'

export async function getStaticPaths() {
  const paths = getAllPostIds()
  return {
    paths,
    fallback: false
  }
}

We’re almost done — but we still need to implement getStaticProps.

Implement getStaticProps

We need to fetch necessary data to render the post with the given id.

To do so, open lib/posts.js again and add the following getPostData function at the bottom. It will return the post data based on id:

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents)

  // Combine the data with the id
  return {
    id,
    ...matterResult.data
  }
}

Then, open pages/posts/[id].js and replace this line:

import { getAllPostIds } from '../../lib/posts'

with the following code:

import { getAllPostIds, getPostData } from '../../lib/posts'

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id)
  return {
    props: {
      postData
    }
  }
}

The post page is now using the getPostData function in getStaticProps to get the post data and return it as props.

Now, let’s update the Post component to use postData. In pages/posts/[id].js replace the exported Post component with the following code:

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  )
}

That’s it! Try visiting these pages:

Blog Data Great! We’ve successfully generated dynamic routes.

Something Wrong? If you come across an error, make sure your files have the correct code. If you’re still stuck, feel free to ask the community on GitHub Discussions. It’d be helpful if you could push your code to GitHub and link to it so others can take a look.

We still haven’t displayed the blog markdown content. Let’s do this next.

------------------------------------------------------------------------

Last update on 14 Feb 2022

---