contentful-gatsby-demo
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 the4111 1111 1111 1111
test card (with any CVV and future expiration date) if you want to place a test order.
Table of contents
- Create the content model
- Import test data into Contentful
- Enrich the product catalog
- Create the Gatsby site and import catalogs
- Create a custom page generator
- Add ecommerce to the site
- 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…