NextJS static page generation feature is extremely useful for pre-generating individual pages. It is particularly useful in pre-rendering home pages, about pages of applications. However, many times we also have a bunch of dynamic pages in our application such as a product page for every product in our store. For such scenarios, we can also pre-render NextJS dynamic route using getStaticPaths function.

In case you are new to this concept, check out our post on the basics of NextJS static generation.

Just like the getStaticProps() function, NextJS also provides a special function getStaticPath() to handle pre-rendering of dynamic routes. In this post, we are going to explore its usage with examples.

1 – NextJS Dynamic Route using getStaticPaths()

Consider that we have an e-commerce store with several products. We want to have a dedicated page for each product. However, we also want to pre-render the page for these products.

While in fixed pages, NextJS automatically pre-renders the page. In case of any external data our component needs for rendering, we can place the same in the getStaticProps() function.

However, in the case of dynamic pages, the default behaviour of NextJS is to not pre-generate the page. This is because NextJS does not know how many possible pages can be present. For example, there can be a couple of products to thousands of products. In other words, NextJS needs more information to be able to pre-render dynamic pages.

Here, we can render NextJS dynamic route using getStaticPaths function

For demo purpose, we will hard-code some products in a JSON file. See below:

{
    "products": [
        { "id": "p1", "title": "Product 1", "description": "This is Product 1"},
        { "id": "p2", "title": "Product 2", "description": "This is Product 2"},
        { "id": "p3", "title": "Product 3", "description": "This is Product 3"}
    ]
}

Now, to render the individual products, we create a dynamic route file named [pid].js. Here, pid is the id of the product.

import fs from 'fs/promises';
import path from 'path';

import { Fragment } from "react";

function ProductDetailPage(props) {

    const { fetchedProduct } = props;

    if (!fetchedProduct) {
        return <p>Loading...</p>
    }

    return (
        <Fragment>
            <h1>{fetchedProduct.title}</h1>
            <p>{fetchedProduct.description}</p>
        </Fragment>
    )
}

async function getData() {
    const filePath = path.join(process.cwd(), 'data', 'dummy-backend.json');
    const jsonData = await fs.readFile(filePath);
    const data = JSON.parse(jsonData);

    return data;
}

export async function getStaticProps(context) {
    const { params } = context;

    const productId = params.pid;

    const data = await getData();

    const product = data.products.find(product => product.id === productId);

    if (!product) {
        return {
            notFound: true
        }
    }

    return {
        props: {
            fetchedProduct: product
        }
    }

}

If we run the application at this point, we get an error when we try to navigate to an individual product page (i.e. /p1).

Error: getStaticPaths is required for dynamic SSG pages and is missing for '/[pid]'.
Read more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value

Basically, the error message is trying to tell us that we need to use getStaticPaths for dynamic server-side generated pages.

Let us now implement the necessary changes.

export async function getStaticPaths() {
   
    return {
         paths: [
             { params: { pid: 'p1'} },
             { params: { pid: 'p2'} },
             { params: { pid: 'p3'} }
         ],
         fallback: false
     }
}

As you can see, we simply return an object from this function. The objects contains a property named paths which contains an array of objects for each product id.

The second attribute is fallback. The value of false signifies that there is no fallback for a non-existing product.

In this case, we have already covered all the three products.

2 – NextJS Fallback Property

In case we decide to only pre-render one of the product pages, we can tweak the paths property as follows:

export async function getStaticPaths() {
   
    return {
         paths: [
             { params: { pid: 'p1'} }
         ],
         fallback: false
     }
}

In this case, if we run the app and trying to access /p2 (i.e. product 2 page), we get 404 error. This is despite the fact that we clearly have the record for product 2 in our dummy data.

The reason for this is the value of fallback as false. In this case, NextJS did not pre-generate the page and since we have set fallback as false, it did not even attempt to generate the page during the request.

To get around this, we can set fallback to true.

export async function getStaticPaths() {
   
    return {
         paths: [
             { params: { pid: 'p1'} }
         ],
         fallback: true
     }
}

This will ensure that NextJS will render the page when we try to access any product other than p1.

This is particularly useful when there are hundreds of product pages in our application. While it is not feasible to pre-generate all the pages as it will be inefficient, we can select the most visited pages for pre-render while others can be handled at the time of request by setting fallback to true.

On the downside, setting fallback as true means the page has to be generated. This might take a bit of time in fetching the product data and can lead to issues when the component starts loading. In order to get around this, we place the below handler in our component code.

if (!fetchedProduct) {
        return <p>Loading...</p>
    }

This makes sure that until the product is not fetched, we show a Loading prompt to the user. Once the details are fetched, we render the actual page. If we don’t keep the above check, we get the below error.

TypeError: Cannot read properties of undefined (reading 'title')

Alternatively, we can also set fallback value as 'blocking'. This makes sure that no check is needed. NextJS always waits for page to regenerate completely before trying to load the component. Refer to the below snippet.

export async function getStaticPaths() {
   
    return {
         paths: [
             { params: { pid: 'p1'} }
         ],
         fallback: 'blocking'
     }
}

3 – Generating Dynamic Paths Automatically

In case we truly want to pre-generate all pages, then it is not correct to hard-code all of the products in the paths array.

In such a case, we can build the paths array dynamically. See below:

export async function getStaticPaths() {
    const data = await getData();

    const ids = data.products.map(product => product.id);

    
    const pathsWithParams = ids.map(id => ({params: {pid: id }}))

    return {
        paths: pathsWithParams,
        fallback: false
    }

}

Here, we get the product data and extract all the ids from the product list. Next, we build the object with the params for each id and assign the same to the paths property.

It is safe to set fallback to false in this case.

Conclusion

With this, we have successfully explored the pre-rendering of NextJS Dynamic Route using getStaticPaths function. We looked at various combinations of properties needed for different scenarios.

Want to explore server-side rendering? Check out this post on NextJS server-side rendering using getServerSideProps.

Want to build a bigger NextJS application? Check out this post on building a NextJS event management application from scratch.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: NextJS

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *