Skip to main content
  1. All Posts/

contentful-gatsby-demo

eCommerce TypeScript

How to Build a Static Site Ecommerce with Contentful, Gatsby, and Commerce Layer

Static sites are the future of Web: fast, secure, and scalable by design. We say that they are the future of e-commerce as well (enterprise included) and this tutorial aims to demonstrate our statement. It is based on the site architecture and development workflow presented in this blog post, except it uses Gatsby as the SSG instead of Jekyll.

Feel free to play with the new live demo here.
Use the 4111 1111 1111 1111 test card (with any CVV and future expiration date) if you want to place a test order.

Table of contents

  1. Create the content model
  2. Import test data into Contentful
  3. Enrich the product catalog
  4. Create the Gatsby site and import catalogs
  5. Create a custom page generator
  6. Add ecommerce to the site
  7. Summary

1. Create the content model

The first step of our tutorial requires a Contentful account. If you don’t have an account, you can create one for free here. Once logged in, create an empty space and take note of the following credentials:

  • Organization ID
  • Space ID
  • Content management access token
  • Content delivery access token

Then create the ~/.contentfulrc file and store all your credentials as follows:

# .contentfulrc

[global]
CONTENTFUL_ORGANIZATION_ID = <your-organization-id>
CONTENTFUL_MANAGEMENT_ACCESS_TOKEN = <your-content-management-access-token>

[Contentful Commerce]
CONTENTFUL_SPACE_ID = <your-space-id>
CONTENTFUL_DELIVERY_ACCESS_TOKEN = <your-content-delivery-access-token>

Now download the content_model.json file from our repo and bootstrap you space as follows:

$ gem install contentful_bootstrap
$ contentful_bootstrap update_space <your-space-id> -j path/to/content_model.json

This will create your content model, that should look like this:

Let’s take a look at each model.

Variant

Variants represent the items that are being sold. The most relevant attribute is Code that will be used as the reference (SKU) to make them shoppable through Commerce Layer (more on this later). Also, note that each variant can be linked to a Size.

Size

Sizes are very simple models with a name, that will be one of “Small”, “Medium”, “Large” for T-shirts or “18×24” for poster and canvas.

Product

Products group variants of the same type (and different sizes). Products can have their own images and descriptions and can be merchandised by category.

Category

Categories are used to group products of the same type. Note that we defined two different associations, one named Products and another named Products (IT). This is a convention that will let merchandisers define a base product selection and sorting and eventually override it by country. When generating the catalog pages for a given country, we will first check if that country (Italy in our case) has a dedicated association. If not, we will fall back to the default one.

Catalog

Catalogs contain a list of categories, that can be selected and sorted independently. Each country will have its own catalog and it will be possible to share the same catalog between multiple countries.

Country

Countries represent the top level of our content model. Take note of the Market ID attribute. Within Commerce Layer, the Market model lets you define a merchant, a price list, and an inventory model. Moreover, all shipping methods, payment methods, and promotions are defined by market. So the Market ID attribute will let us associate different business models to each country or share the same market configuration between multiple countries.

2. Import test data into Contentful

Once created the content model, we need to populate Contentful with some test data. To do that, create a free developer account on Commerce Layer. You will be prompted to create a sample organization and seed it with test data. In a few seconds, your sample organization will be populated with about 100 SKUs like the following:

The seeder will also create two markets (EU and US) and an OAuth2 sales channel application.
In order to export the sample data to Contentful we need to create a Contentful application within Commerce Layer. Take note of the application credentials, including the base endpoint.

Then create the ~/.commercelayer-cli.yml file on your local environment and store all your credentials as follows:

# .commercelayer-cli.yml

commercelayer:
  site: <your-base-endpoint>
  client_id: <your-client-id>
contentful:
  space: <your-space-id>
  access_token: <your-content-management-access-token>

Finally, export your sample data into Contentful by running the following commands:

$ gem install commercelayer-cli
$ commercelayer-cli export contentful

Recently we released an UI extension that lets you visually associate an SKU to any model on Contentful. It brings the SKU code into the content model, so that you can make it shoppable on the front-end by leveraging the Commerce Layer API. Check it out!

3. Enrich the product catalog

The SKUs that we exported from Commerce Layer to Contentful created a list of variants and products, using the SKU references to automatically associate variants to products. Now we need to enrich the catalog on Contentful with product images, descriptions and categories. For the sake of simplicity, we skip this part of the tutorial. Anyway, it’s important to notice how this process is independent of the ecommerce platform. Content editors are not locked into any templating system or front-end framework. They are free to create any content. The product prices and the stock will be managed by Commerce Layer transparently, as well as the shopping cart and checkout experience.

4. Create the Gatsby site and import catalogs

Now that we have all our content and commerce models set up, it’s time to create the website.
First of all, follow Gabtsby quick start and prepare the environment.

$ npm install -g gatsby-cli
$ gatsby new contentful-gatsby-demo
$ cd contentful-gatsby-demo

Then run the following command to install the Contenful plugin for pulling content types, entries, and assets into Gatsby from your Contentful space:

$ npm install --save gatsby-source-contentful

Create a .env file and store your credentials as follows:

# .env
CONTENTFUL_SPACE_ID=YOUR_SPACE_ID
CONTENTFUL_DELIVERY_ACCESS_TOKEN=YOUR_ACCESS_TOKEN

Add the following code to gatsby-config.js:

{
  resolve: `gatsby-source-contentful`,
  options: {
    spaceId: process.env.CONTENTFUL_SPACE_ID,
    accessToken: process.env.CONTENTFUL_ACCESS_TOKEN
  }
}

If you want to learn more about environment variables and how to use them to customise your site’s behavior in different environments, please check this Gatsby custom configuration reference guide.

What we need now is to generate a page for each catalogue, category and product, all scoped by country and language. Since Gatsby doesn’t generate data pages out of the box, we need to create a custom generator.

5. Create a custom page generator

Let’s use this Gatsby tutorial on how to programmatically create pages from data, as a starter. The custom generator will iterate over the imported data and create all the required pages.
Add the following code to gatsby-node.js:

// Read all data from Contentful

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(`
    query {
      allContentfulCountry {
        edges {
          node {
            node_locale
            code
            catalogue {
              name
              node_locale
              categories {
                name
                products {
                  name
                  contentful_id
                }
                contentful_id
              }
            }
          }
        }
      }
    }
  )
}

Now that we have imported all the data from Contentful, we need to create a slug for our pages.

// ...

result.data.allContentfulCountry.edges.forEach(({ node }) => {
  // Catalogue page
  createPage({
    path: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/`,
    component: path.resolve(`./src/templates/CatalogPage.tsx`),
    context: {
      // Data passed to context is available in page queries as GraphQL variables.
      slug: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/`,
      language: node.node_locale,
      shipping: node.code,
      pageTitle: node.node_locale === 'it' ? 'Categorie' : 'Categories'
    }
  })
  node.catalogue.categories.map(c => {
    const categorySlug = c.name
      .trim()
      .toLowerCase()
      .replace(' & ', ' ')
      .replace(/s/gm, '-')
    // Category page
    createPage({
      path: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/${categorySlug}`,
      component: path.resolve(`./src/templates/CategoryPage.tsx`),
      context: {
        // Data passed to context is available in page queries as GraphQL variables.
        slug: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/${categorySlug}`,
        language: node.node_locale,
        shipping: node.code,
        categoryId: c.contentful_id,
        categorySlug,
        pageTitle: c.name.trim()
      }
    })
    c.products.map(p => {
      const productSlug = p.name.trim().toLowerCase().replace(/s/gm, '-')
      // Product
      createPage({
        path: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/${categorySlug}/${productSlug}`,
        component: path.resolve(`./src/templates/ProductPage.tsx`),
        context: {
          // Data passed to context is available in page queries as GraphQL variables.
          slug: `/${node.code.toLowerCase()}/${node.node_locale.toLowerCase()}/${categorySlug}/${productSlug}`,
          language: node.node_locale,
          shipping: node.code,
          categoryId: c.contentful_id,
          categorySlug,
          categoryName: c.name.trim(),
          productId: p.contentful_id,
          pageTitle: p.name.trim()
        }
      })
    })
  })
})

Now we are ready to create our templates. For example, the category page one will look like this:

// CategoryPage.tsx

import React from 'react'
import Breadcrumb from '../components/Breadcrumb'
import Products from '../components/Products'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import useShoppingBag from '../hooks'
import SEO from '../components/seo'

export default (props) => {
  const {
    pageContext: { language, shipping, slug, categorySlug, pageTitle }, data
  } = props

  const products =
    shipping.toLowerCase() === 'it' &&
    data.contentfulCategory.products_it &&
    data.contentfulCategory.products_it.length > 0
      ? data.contentfulCategory.products_it
      : data.contentfulCategory.products
    
  const [status, setStatus] = useShoppingBag()
  
  return (
    <Layout
      {...props}
      shoppingBagStatus={status}
      setShoppingBagStatus={setStatus}
    >
      <SEO title={pageTitle} />
      <Breadcrumb
        shop={shipping.toLowerCase()}
        lang={language}
        uri={slug}
        categorySlug={categorySlug}
        categoryName={data.contentfulCategory.name.trim()}
      />
      <Products
        shop={shipping.toLowerCase()}
        lang={language.toLowerCase()}
        data={products}
        categorySlug={categorySlug}
      />
    </Layout>
  )
}

export const query = graphql`
  query Products($categoryId: String, $language: String) {
    contentfulCategory(
      contentful_id: { eq: $categoryId }
      node_locale: { eq: $language }
    ) {
      name
      products {
        contentful_id
        name
        image {
          file {
            url
          }
        }
        reference
        variants {
          code
        }
      }
      node_locale
      products_it {
        contentful_id
        name
        image {
          file {
            url
          }
        }
        reference
        variants {
          code
        }
      }
    }
  }
`

Follow the example above to create the catalogue and product page templates. Then run…