Create a product listing with Next.js - Part 3

by Jaeriah Tay on December 1, 2020
See live demo

In this section, you will get to creating a single product detail page.

Create dynamic routes with getStaticPaths

First, view the index page in the browser and click one of the product items, you will notice that you are navigated to a 404 page. Next.js is automatically doing that for us if we try to route to a page that does not exist. Now let's fix that and create some dynamic routes and static pages.

As you notice in our product listing, we are using next/link to handle client-side navigation to the dynamic routes we will create. Commerce.js allows us to work with various properties in the products endpoint such as the description and other meta data so we will be using some of the properties to output in a dynamically routed page. Now create a file named [permalink].js in a new product/ directory - pages/product/[permalink].js. The variable inside the square brackets will evaluate to each product detail page's permalink. The permalinkproperty is being pulled from the Chec API's product dataproduct.permalink`.

In this component, there are a few moving parts we will need to create:

  • A function called getStaticPaths from Next.js that will pre-render paths based on the parameter passed in
  • The getStaticProps which you have already used earlier to retrieve a single product with the parameter permalink
  • The markup for the product detail page

Following the code snippet below, export the function getStaticPaths, then make a call to the Chec API products endpoint so that we can map through the products list and return all the paths with product.permalink. You need to also provide the required fallback key to the value false. Setting the value to false will tell Next.js to render 404 pages when paths don't exist.

import { commerce } from '../../lib/commerce';
import Link from 'next/link';
import ArrowLeft from '../../assets/arrow-left.svg';
import ArrowRight from '../../assets/arrow-right.svg';

// This function gets called at build time on server-side.
export async function getStaticPaths() {
  const { data: products } = await commerce.products.list();

  return {
    paths: products.map((product) => ({
      params: {
        permalink: product.permalink,
      },
    })), 
    fallback: false,
  }
}
export default ProductDetailPage;

Get single product with getStaticProps

Next, export the function getStaticProps then pass in the required context params key. The params key has the route parameter that will evaluate to permalink: { ... } as that is the property used to define our routes. Note that the variable inside the square brackets would match this property. Let's also destructure permalink out of params and use it to pass in as a parameter to the commerce.products.retrieve Commerce.js function. Either a product.id or product.description is required to retrieve the product from the Chec API. You will also need to define the type as a second argument. Lastly, return the product as props and the key revalidate. This optional key in Next.js define the number of seconds before a static page will get re-generated again. It is using the new feature Incremental Static Regeneration which has the benefits that emulates SSR but statically re-generates existing pages when there are updates or adds a new page.

// This function gets called at build time on server-side.
export async function getStaticPaths() {
  const { data: products } = await commerce.products.list();

  return {
    paths: products.map((product) => ({
      params: {
        permalink: product.permalink,
      },
    })), 
    fallback: false,
  }
}

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

  const product = await commerce.products.retrieve(permalink, {
    // Must include a type value
    type: 'permalink'
  });

  return {
    props: {
      product,
    },

    revalidate: 60,
  }
}
export default ProductDetailPage;

Create the product detail template

The last bit of this component is to now render the product detail page with all the returned data you get from the functions above.

Below the function getStaticProps, start by creating a function component and name it ProductDetailPage. Pass in the product parameter which you will use to access each product's image, name, description, and price via product.media.source, product.name, product.description and price.

With the Chec API, product descriptions return HTML which means if we were to render out product.description, we would get a string that returns the html tags along with the description. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack. You can set HTML directly from React, but you have to type out dangerouslySetInnerHTML and pass an object with a __html key, to remind yourself that it might be dangerous. But because we know we can trust the API responses, this is the best approach to take to render out our product description.

const ProductDetailPage = ({ product }) => {

  return (
    // Add head tag
    <div className="product-detail">
      <img className="product-detail__image" src={product.media.source} alt={product.name} />
      <div className="product-detail__info">
        <Link href="/">
          <a className="product-detail__back">
            <ArrowLeft className="product__icon" width={42} height={42} />
            <p>Back to products</p>
          </a>
        </Link>
        <div className="product-detail__details">
          <h1 className="product-detail__name">{product.name}</h1>
          <div
            className="product-detail__description"
            dangerouslySetInnerHTML={{__html: product.description}}
          ></div>
          <div className="product-detail__price">
            {product.price.formatted_with_symbol}
          </div>
        </div>
      </div>
      <button
        name="View item"
        className="product-detail__btn"
      >
      <span>Add to cart</span>
      <ArrowRight className="product__icon" width={48} height={48} />
    </button>
  </div> 
  )
}

If you have your server running you should now see the complete application with a product listing page and dynamically routed product detail pages!

Customization and extendability

The fun does not end here with the Chec API and Commerce.js. You can continue to extend the application to include cart functionalities and a custom checkout flow. Some other enhancements to consider:

  • Adding shipping zones and enable shipping options for each product in your dashboard
  • Customizing the styling
  • Leveraging webhooks to automate post checkout actions
#nextjs

About the author, Jaeriah Tay

Having been in communications design and entrepreneurship before joining Commerce.js, Jaeriah now contributes to both development and design and is passionate about both aspects of product.