CodePaper
About
Services
BlogContact

Filed Under: Ecommerce

Building an Ecommerce site with Gatsby, Contentful and Snipcart

Building an Ecommerce site with Gatsby, Contentful and Snipcart

By Alex Quasar
gatsby-snipcart-contentful

The absence of speed kills. This is a known fact, and there have been numerous articles relating to how slow websites kill traffic. In fact, a study by Google shows that over half of your mobile visitors leave a page that takes longer than 3 seconds to load. As mobile users continue to grow, the demand and expectations for faster page loads are greater than ever.

It is not just mobile users either that are impatient. A study from Amazon showed that 100 ms of page speed cost them 1% in sales!

I experimented with several large eCommerce platforms like Shopify and Woo-commerce. Woo-commerce comes with the baggage of WordPress and WordPress is intrinsically slow and clunky. Shopify is also very slow, especially when you have lots of products. When I created and launched a Shopify store, with about a dozen or so products I experience load speeds of 5 seconds or more on desktop with fast WiFi. This speed was agonizing and I knew I needed to build a better site with faster load times and better user experience. I also knew that if I wanted to move to complete customization and freedom than I had to move beyond any platform like WordPress or Shopify.

Since faster page loads translate to more site visits, better user experience, more conversions and ultimately greater revenue, I was determined to build an eCommerce site with speed as the #1 priority.

There is also the developer experience. While Woo-commerce and Shopify are easy to set up out of the box, customization is more difficult, especially if you are not familiar or specialized in php or Liquid.

Enter Gatsby. Gatsby markets itself as a blazingly fast static site generator. Being familiar with React, this open source framework, marketed for speed and built on top of React was an obvious go to choice.

The next task was finding a modern CMS solution that easily integrated with Gatsby and could provide an easy and scalable way to create, update and manage products. For this, Contentful comes to the rescue.

Finally, the last piece was how users were going to buy the products. I went with Snipcart because it provides an easy to implement solution, with minimal costs compared to other related competitors. It’s new V3 version is the slick, streamlined and optimized checkout flow I was after. With Snipcart, you also get a product that is tested to increase your sales and conversions. Developer mode is free and you only pay once you go live. Starting at $10/month it is one of the more affordable options with lots supported e-commerce payment gateways, like Stripe, PayPal and Square.

With these three pillars in place, I was ready to build my prototype. In this article, I outline a step by step process of how I did this.

Prerequisites

Some knowledge of Gatsby would be helpful. In addition, basic React, JavaScript developer knowledge is assumed. I also assume you have node installed and are familiar with npm.

Overview

We are going to be creating a bare bones, but extremely fast, powerful and scalable ecommerce starter using some amazing modern technology. There will be a few optional sections which if you are familiar with you can skim or skip over. Let’s dive right in.

1. Starting up Gatsby (optional)

Let’s jump straight into Gatsby. You will need to install Gatsby on your computer.

Install the Gatsby CLI

npm i -g gatsby-cli

Create a new site and give it a name like below

gatsby new fashion-two

Alternatively, if you are already inside your root project folder you can simply go

gatsby new .

This will create a Gatsby Starter Default Project. Navigate to the inside the folder ( if needed ) and start the development server.

gatsby develop

You should now see a new Gatsby site on localhost: 8000 with a funky looking astronaut waiter guy, or some other welcome page.

2. Initial Setup

Lets install some dependencies right now. I will install the other dependencies we need later on as needed.

npm i @emotion /core @emotion /styled emotion-theming gatsby-plugin-emotion gatsby-image

These dependencies should be enough to get started.

In this optional section, I will quickly run through some of overhead I use for a given project, will mostly keeping the existing starter file intact for brevity.

If you head over to the layout.js file we can clean this up by getting rid of most of the stuff and keeping the following code

// layout.jsimport React from "react"
import PropTypes from "prop-types"

import Header from "./header"
import "./layout.css"

const Layout = ({ children }) => {
return (
<>
<Header siteTitle="Gastby Fashion Demo" />
</>
)
}

Layout.propTypes = {
children: PropTypes.node.isRequired,
}

export default Layout

I will keep the standard layout.css file which will have some global css styles that we want to use in our app. You could elect to replace this with an scss file to keep your global styles more modular and organized.

There will be some minimal styling in this app, using Emotion

To add emotion, add the following to the plugins array in the gatsby-config.js file

{ resolve: `gatsby-plugin-emotion` },

I want my colors to be consistent and easily maintainable. You could use scss variables but since we are using Emotion, I will insert an emotion theme into the layout component to make all the color variables available to any component. This way, if we decide to change the shade of red we are using, we only need to in one place. Make sure you have the following imported dependencies installed.

Once this is done, the starter layout.js file will look something like this:

import React from 'react';
import PropTypes from 'prop-types';

import { ThemeProvider } from 'emotion-theming';
import styled from '@emotion/styled';

import Header from './header';
import './layout.css';
import { Footer } from './footer';

const theme = {
colors: {
primary: 'rgb(92, 52, 145)',
primaryDark: 'rgb(63, 3, 122)',
primaryLight: 'rgb(148, 103, 206)',
primaryVeryLight: 'rgb(232, 213, 250)',
primaryTransparent: 'rgba(92, 52, 145,0.2)',
white: '#fff',
black: '#1a1a1a',
blackTransparent: 'rgba(41, 43, 46, 0.4)',
red: 'rgb(218, 18, 31)',
},
pageWidth: {
fixed: '800px',
},
screenSize: {
mobileL: '600px',
},
};

const Main = styled.section``;

const Layout = ({ children }) => {
return (
<>
<ThemeProvider theme={theme}>
<Header siteTitle="Gastby Fashion Demo" />
<Main>{children}</Main>
<Footer />
</ThemeProvider>
</>
);
};

Layout.propTypes = {
children: PropTypes.node.isRequired,
};

export default Layout;

3. Contentful

Contentful is a modern, popular CMS where we will be storing our products. We will be using Contentful to create a Bag content model. This content model acts like a blue print or schema for the bag products we are going to add.

After signing up here create a new content model by clicking on Content Model -> Add Content Type.

My new content model is called fashionTwoBags. Inside the Content Model we can declare the field name, field types and any associated validation along side it in a super intuitive user interface. Any product that is a bag on our store will have the following fields below.

undefined

The slug field is a short text, that we should set to be required. In addition, we can specify it to have an appearance of “slug” inside the field settings.

The tags field is a short text list field that is useful for an storing an array of values. For this case I will use tags to describe certain attributes of my bag product such as classic, modern, new. etc.

The description field is a rich text field for long text where we have access to markdown options like heading and paragraph and styles like bold, italic and underline. In addition, this WYSIWYG content editor lets you embed other assets and entries directly inside.

We cannot do any custom styling inside the Contentful UI. However, later on we will add in custom formatting to the rendered rich text content.

The rest of the fields are relatively straight forward. Though your fields might be different with different names, the basic principles are the same. I have kept every field a required field with the exception of otherImages. I left this field optional, because sometimes we might or might not have many images for a particular product.

In Contentful, we can also create a reference to another content model. References (aka relationships) to other content models could come in handy. You might have Brand content model which would store fields about the brand such as the name, size, founder and history. Another good example to create references is for authors and articles. For simplicity, this demo will not use any references to other content models, but you can easily set up these relationships in Contentful.

4. Contentful — Creating Content

After creating our new Content Model, lets navigate to Content -> Add Entry. Here we can add some new content based on the fields we set up. My favorite button for this is Actions -> Duplicate which allows us to easily duplicate content.

For content and images what should you use? You should use whatever you are selling on your website obviously. If you are a developing for a client, Lorem Ipsum generator or any other variation is a handy tool. For images, Adobe Stock is great but expensive. Free alternatives include unsplash and pixabay.

After creating several new product entries and publishing them lets create some GraphQL queries to render some content on the screen of our website.

5. Rendering all our Products in Gatsby

There is lots of other tutorials using GraphQL with Gatsby, so I will go fairly quick through this section.

First go to or create a gatsby-config.js file In this file, we store our site metadata and our other plugins. At the top if this file add

// gatsby-config.js
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`,
});

this allows us to bring in our environment variables stored in the .env.development and .env.production files. You can learn more about environment variables at https://gatsby.dev/env-vars

Also, we will add the following to the plugins array

// gatsby-config.js
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: process.env.CONTENTFUL_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
},
}

Inside the .env.development file you should have something like

// .env.development
CONTENTFUL_ID=YourId
CONTENTFUL_ACCESS_TOKEN=YourToken CONTENTFUL_PREVIEW_ACCESS_TOKEN=aaa111

You will find these values for your app by going to Contentful Settings -> Api Keys => Access Tokens

Note: Make sure you do NOT surround the values in quotes and you do not commit the .env files to GitHub

Finally let’s install some Contentful plugins:

npm i gatsby-source-contentful @contentful/rich-text-react-renderer @contentful/rich-text-types

If you run into an error, make sure you have some content inside your Contentful models!

After restarting the gatsby dev server, go to http://localhost:8000/___graphql

On the left hand side of this Graphiql interface you should see an Explorer with a host of graphQL queries. You can build the queries using the explorer interface. Another handy command is using ctrl + spacebarto get a preview of the selection you can use.

With the Contentful plugin successfully installed you should also see your Contentful queries in there ( after refreshing page ).

Since this is not a graphQL tutorial, I will not explain the query in depth. I used the following query to get all entries from the Contentful content model. The fields below are specified below.

{
allItems: allContentfulFashionTwoBags {
nodes {
id
productName
productSlug
shortDescription
price
discountPrice
tags
rating
displayBadge
mainImage {
fluid(quality: 90, maxWidth: 300) {
src
}
}
}
}
}

Running this query inside Graphiql should return a response object with all of our fields.

Next we are going to need to call this query inside of our index.js file. To do this we will need to just import graphql from Gatsby and render the page query, which we created above like so:

import React from 'react';
import { Link, graphql } from 'gatsby';
import styled from '@emotion/styled';
import Image from 'gatsby-image';
import Layout from '../components/layout';

export const query = graphql`
{
allItems: allContentfulFashionTwoBags {
nodes {
id
productName
productSlug
shortDescription
price
discountPrice
tags
rating
displayBadge
mainImage {
fluid(quality: 90, maxWidth: 300) {
...GatsbyContentfulFluid_withWebp
}
}
}
}
}
`;

const StyledImage = styled(Image)`
width: 20rem;
height: 20rem;
`;

const index = ({ data }) => {
const products = data.allItems.nodes;
return (
<Layout>
{products.map(product => (
<Link key={product.productSlug} to={`/products/${product.productSlug}`}>
<h4>{product.productName}</h4>
<div> $ {product.price} USD</div>
<div>
<StyledImage fluid={product.mainImage.fluid} />
</div>
</Link>
))}
</Layout>
);
};

export default index;

The result of this query is a data object which we pass into the index page component like shown above.

Also note we have replaced src with …GatsbyContentfulFluid_withWebp because we want to take advantage of Gatsby’s built in image optimization features, which you can read more about here. This new Contentful hook will return an array of items, which are all of our Contentful entries.

Great, we have all product names and a link to each product slug along with an Image optimized by Gatsby.

You might have noticed that the links currently go to a 404 page. In the next section we will fix this by writing some code to create a new page for each of these products programmatically.

6. Rending each product page in Gatsby

We want to create a new page programmatically for each of our content entries in our fashionTwoBags content model. We will want to write a GraphQL query that outputs all of our product slugs and creates a page for each of these slugs. Luckily, we can use the Gatsby createPages API to do this. In the code below I have written some comments to help beginners.

// gatsby-nodes.js const path = require ( 'path' ) ;// use gatsby create pages api and query for all product slugs
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions;
const { data } = await graphql(`
{
allBags: allContentfulFashionTwoBags {
nodes {
productSlug
}
}
}
`);

// create page for each product and list
// them all in /products/:productSlug
data.allBags.nodes.forEach(item => {
createPage({
path: `products/${item.productSlug}`,
component: path.resolve('./src/templates/Bag.js'),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: item.productSlug,
},
});
});
};

You may have noticed, we need to create a new template component Bag.js.
For now, create a simple react component with some dummy text inside.

Inside GraphiQL ( http://localhost:8000/___graphql) you see another query called contentfulFashionTwoBags. This returns a bag instead of all the bags. To specify which bag to return we will need to create a new query. In this query we will create a new slug variable and pass it in as type string

query bagTemplateQuery($slug: String!) {

item: contentfulFashionTwoBags(productSlug: { eq: $slug }) {
id
productSlug
productName
shortDescription
description {
json
}

mainImage {
fluid {
src
}
}
otherImages {
fluid {
src
}
}
price
discountPrice
tags
rating
color
}
}

Inside the query variables, at the bottom of the interface, pass in a specific slug name from your content.

{
"slug":"monnet-lime-green-bag"
}

Recall that our description field inside Contentful was a Rich Text Field that returns a JSON data object. It looks rather intimidating here to parse it and render it on the page. We will use a library to help us render this.

We can create our product template called Bag.js and use the query we made in GraphiQL above replacing src with …GatsbyContentfulFluid_withWebp like earlier.

I am also invoking the library @contentful/rich-text-react-renderer to helps us format the json data object from the description field.

// src/templates/Bag.js
import React from 'react';
import styled from '@emotion/styled';
import { graphql } from 'gatsby';
import Img from 'gatsby-image';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import Layout from '../components/layout';
// run template query
export const query = graphql`
query BagTemplateQuery($slug: String!) {
item: contentfulFashionTwoBags(productSlug: { eq: $slug }) {
id
productSlug
productName
shortDescription
description {
json
}
mainImage {
fluid {
...GatsbyContentfulFluid_withWebp
}
}
otherImages {
fluid {
...GatsbyContentfulFluid_withWebp
}
}
price
discountPrice
tags
rating
color
}
site {
siteMetadata {
siteUrl
}
}
}
`;
const StyledImage = styled(Img)`
width: 30rem;
height: 30rem;
`;
const BagTemplate = ({ data: { item } }) => {
const options = {
// options for rich text formating
};
return (
<Layout>
<h2>{item.productName}</h2>
<div>{item.rating} stars</div>
<div>{item.shortDescription}</div>
<StyledImage fluid={item.mainImage.fluid} />
{/* render the rich text format description */}
<main>{documentToReactComponents(item.description.json, options)}</main>
</Layout>
);
};
export default BagTemplate;

After restarting the dev server, the links to each product page should work and we should have a unique product page for each slug. We are making good progress!

Note, I have also added a small query at the end so we can get our site url, which will be used in our Snipcart section

To render our rich text format, we called the function documentToReactComponents which excepts a JSON object and an options object as arguments.

As mentioned earlier, we can custom style the block, marks and inlines from the Contentful WYSIWYG content editor.

Add the following to the Bag.js file to style the paragraph tags orange and the hyperlinks purple with an orange background.

// import rich-text-types 
import { BLOCKS, MARKS, INLINES } from '@contentful/rich-text-types'
// Add in some styling
const Bold = styled.span`
font-weight: bold;
color: red;
`;

const P = styled.p`
color: orangered;
`;

const StyledHyperLink = styled.span`
color: purple;
padding: 1px 2px;
background: orange;
cursor: pointer;
`;

// render the styling
const RTFBold = ({ children }) => <Bold>{children}</Bold>;
const Text = ({ children }) => <P>{children}</P>;
const HyperLink = ({ children }) => (
<StyledHyperLink>{children}</StyledHyperLink>
);

// modifying the options
const options = {
renderMark: {
[MARKS.BOLD]: text => <RTFBold>{text}</RTFBold>,
},

renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[INLINES.HYPERLINK]: (node, children) => (
<HyperLink>{children}</HyperLink>
),
},
};

We have now just created custom rendering styles that is scoped only to the Bag.js template using emotion CSS in JS. Of course you probably don’t want to have purple colored, orange background hyperlinks for your template, but the options for customization is there. For more information on rendering rich text, refer to this library here

We now have all our products stored in Contentful, a robust content model and products on the screen. The last major component of our project is the checkout process so users can purchase our products.

7. Snipcart

Now that we have some content rendered, we can add in Snipcart to aid our checkout process. Snipcart v3 has been recently released (Oct 2019) so I will be using this. You will also need to set up a payment gateway. I used Stripe to do this. If you do not have a Stripe account and want to use Stripe as the payment gateway you will also need to set up a Stripe developer account.

Sign up for Snipcart: https://snipcart.com/

Sign up with Stripe: https://stripe.com/en-ca

Inside the Snipcart dashboard there is two main navigation tabs. The left black one, which has information about your orders, sales, abandoned carts, discounts, products and a whole slew of other features. The other navigation tab is the store configurations/ account navigation bar which can be accessed by clicking on the top right user icon.

In the middle top of our dashboard, we can toggle between live mode and test mode. Make sure test mode is selected.

In the account navigation you can find your api keys under Account -> API Keys.

In the store configuration navigation under Domains and URL you will need to add your site domain name. At this point you might not yet have a domain. If that is the case, revisit this step at the end of this article ( we will be deploying with Netlify and creating a temp url which you can use )

In your preferred payment gateway , get the associated API keys. For Stripe, you can visit https://dashboard.stripe.com/dashboard

Install the snipcart gatsby plugin

npm i gatsby-plugin-snipcart

In the gatsby-config.js file we will need to add the following into our plugin array

{
resolve: 'gatsby-plugin-snipcart',
options: {
apiKey: process.env.SNIPCART_API,
autopop: true,
js: 'https://cdn.snipcart.com/themes/v3.0.0/default/snipcart.js',
styles: 'https://cdn.snipcart.com/themes/v3.0.0/default/snipcart.css',
},
},

Near the top will also need to add our URL, which you can do now or after you have a domain url set up:

// Define site URL here
let URL;
if (process.env.NODE_ENV === 'production') {
URL = 'https://gatbsy-ecommerce-demo.netlify.com';
} else {
URL = 'http://localhost:8000';
}

module.exports = {
siteMetadata: {
title: `Gatsby Ecommerce Demo`,
description: `Your next ecommerce Gatsby site powered with Contentful and Snipcart.`,
author: `aquasar.io`,
siteUrl: URL,
},

Inside the .env.development file add your SnipCart API key without quotes.

SNIPCART_API=aaa111 
STRIPE_SECRET_KEY=sk_test_aaa111 GATSBY_STRIPE_PUBLIC_KEY=pk_test_aaa111

The Snipcart api key to use is the Public Test Api key to use in bold at the very top of the SnipCart site. Restart the Gatsby dev server and add the following code into the Bag.js template where you want the checkout button to appear.

// src/templates/bag.js 
<button
className={`snipcart-add-item`}
data-item-id={item.id}
data-item-name={item.productName}
data-item-image={item.mainImage.fluid.src}
data-item-price={item.discountPrice ? item.discountPrice : item.price}
data-item-url={`${site.siteMetadata.siteUrl}/products/${item.productSlug}`}
>
Add to Cart
</button>

The checkout button should now work and you should see a popup with the cart details when you hit ‘Add to Cart’

You can test this checkout flow using some VISA test numbers. This won’t work on localhost without some additional setup. If you need to test on localhost than refer to this article here

Under Payment Gateway you can connect and choose from a number of options such as Stripe, PayPal and Square. You can also use PayPal express in addition to the gateway of your choice. I believe Stripe at this time has the best rates for a new startup eCommerce site. Rates however will vary by region and volume. I currently see a starting rate of 2.9% per successful card charge + $0.30 with no monthly fees.

Under Orders & Invoices you can add your Company Name and Company Logo. This will be sent to as a order confirmation to a customer when they purchase.

In the Snipcart store configurations there is a ton of other options like Shipping, Taxes and Email Templates which we will not cover as they are not essential features. You should however, play around with these settings and customize them for your business or client.

8. Deploying to Netlify

Netlify is an amazing service for Front-End modern sites. If you haven’t done so, please create a Netlify account. Before deploying to Netlify, lets push this repo to Github.

Before pushing to Github make sure you do NOT push your sensitive API keys to that are stored in your .env.development and .env.production files. For this demo both development and production files will have the same keys and be exactly the same. The .env.production file here is merely for reference. Make sure you go to .gitignore and add in the following to make sure this files wont be sent to Github.

// .gitignore
# dotenv environment variables file
.env
.env.production
.env.development

Push these files to github (In VS Code Integrated Terminal)

git add .
git commit -am 'first commit'
git push origin master

Now inside Netlify, you need to find the repo we just pushed and deploy your new site from github.

The deploy will fail unless we add our environment variables inside the deploy settings. This can be found by going to Settings -> Build & Deploy -> Environment -> Environment Variables -> Edit Variables.

environment-variables-netlify.png

Add all the keys and values exactly the same as in the .env.development or .env.production files.

We can then select Trigger Deploy -> Deploy site to redeploy.

This usually takes a few minutes but you should have a new site up and running.

Netlify will give you a random url that ending in .netlify.com

You can add your own url under the domain settings, or change this random name to a more preferable one by going to Custom domains -> Edit Site Name

9. Testing

If you did not have a domain name, at this point you should add in the Netlify created domain name into the Snipcart Domain and URLs settings. You can add as many domains or sub domains as you like.

snipcart-dashboard.png

We have not yet tested our checkout process. Depending on your region you can use for example

USA : 4242 4242 4242 4242

CANADA : 4000 0012 4000 0000

The csv or security code can be any three digit number and the date can be any date in the future. You can also use any name and valid address you like.

10. Creating a Contentful Hook

If we update our products than ideally we want to automatically trigger a site deploy so that our new products are available on your site without having to manually deploy our site again.

To do this, create a new hook in Netlify and then go over to Contentful and past this new hook by going to Contentful -> Settings -> Webhooks

We are now automatically triggering site deploys once we add new products. You can review the webhook settings to make sure you only trigger a deploy when you really need to, ie publishing new products or content. Netlify has started to charge a fee for build limits over a 300 minutes/month, so keep that in mind.

Recap:

We have a simple MVP ecommerce site that is powered by Gatsby, Contentful and Snipcart. It has been a pleasure to share my ecommerce JAMstack experience with you and I hope this article has helped you with your Ecommerce project. If you have any questions or would like to report any errors, please reach out to me.

The full github repo is at

https://github.com/arhoy/gatsby-ecommerce-demo

To view this demo live please:

https://gatbsy-ecommerce-demo.netlify.com/#/


Originally published at https://aquasar.

Continue Reading

Navigation
HomeAboutServicesBlogContactPrivacy PolicyTerms & Conditions
Follow Us
Services
Websites
Contact Us

codepaper.dev@gmail.com

587.501.7726

Subscribe To Our Newsletter

Stay up to the date with the latest in Automation and CodePaper by subscribing to our newsletter

Special Thanks To