Using Gridsome for E-Commerce [Tutorial & Live Demo]

In a rush? Skip to technical tutorial or live demo.

As the latest dev hire for Snipcart, I was put to the test quickly.

My mission? Craft an e-commerce side-project using our shopping cart with an imposed technology, in a limited amount of time.

The technology in question? Gridsome, the Jamstack framework for Vue.js.

So, I rolled up my sleeves and got to work.

My efforts resulted in a Gridsome e-commerce demo. I documented my process in a step-by-step tutorial ready for you to follow along.

This tutorial explores how to:

  • Create a Gridsome project
  • Enable e-commerce functionalities with Snipcart
  • Craft product listings & pages
  • Configure the template routing
  • Leverage GraphQL with Gridsome

I think I did a decent job. Let’s see, shall we?

What’s up with Gridsome?

gridsome-tutorial

In case you don’t know about it, Gridsome is a modern website generator for Vue.js.

What does “modern” mean here? As a tool, it falls under the trending Jamstack umbrella and can be used to generate static websites, progressive web apps (PWA), or single-page apps (SPA). Whatever the type of frontend platform you want to create, Gridsome makes it easy to connect to any CMS or data source using GraphQL.

This first post we wrote about Gridsome explained how to use the GraphQL API to fetch data from an Airtable database.

Familiar with the React-powered Gatsby? Well, Gridsome can be considered the equivalent for Vue.

how-gridsome-works

Great overview of how Gridsome works

Their latest major release was Gridsome v0.7, which enables Vue components in Markdown, a new schema API, better template configuration & file-based dynamic routing.

I would recommend basic knowledge about HTML, CSS, and Vue.js before jumping into Gridsome. However, don’t be scared by it if you’re not a GraphQL expert; Gridsome is actually a great way to learn it.

Why use Gridsome for your e-commerce projects?

Since e-commerce is the specific use case concerning us here, let’s see what benefits Gridsome can bring to this kind of project.

→ Fast by default - With e-commerce, UX is everything. You don’t want customers stuck in front of a loading page for too long. At the risk of sounding like a broken record, one of the main benefits of the Jamstack is performance. Gridsome is no exception. With features like code splitting, asset optimization & progressive images out of the box, speed is taken care of!

→ SEO-Friendly - A well-optimized online shop can give you an edge over your competition. If performance is already a big plus for SEO, you also want search engines to crawl your pages content fully. By loading pages as static HTML before converting them into Vue-powered SPAs, Gridsome does just that.

→ PWA-ready - Want to push customer experience even further? Consider going the PWA route, enabling reload-free offline shopping. We’ve explored PWA e-commerce in-depth in this post.

→ Plugins for dynamic features - When your e-commerce project scales, you might need more dynamic functions to handle users’ shopping needs. Gridsome already offers an array of plugins to expand your website’s functionalities. There’s a straight-up Shopify plugin available that might fit your needs if you prefer using something else than Snipcart!

However, it’s also easy to opt for a third-party shopping cart integration with Gridsome. That’s precisely what I’ll do in the tutorial below, plugging our HTML/JS shopping cart into a static website.

Let’s do this!

Build an e-commerce website using Snipcart and Gridsome

gridsome-ecommerce-tutorial

1. Set up the development environment

To manage your Gridsome project, install Gridsome CLI globally by using the following command in a terminal:

npm install --global @gridsome/cli

2. Create a Gridsome project

To create your project, use the following command:

gridsome create snipcart-gridsome

Now go to the project directory:

cd snipcart-gridsome

And start the local server:

gridsome develop

A starter project should now be visible at the specified port in your terminal. You’re now ready to code!

3. Add Snipcart assets

Let's first install Snipcart by overriding Gridsome default index.html and add the required assets in the index.html file.

With your favorite code editor, open the main.js file.

Your index.html file should now look like this:

<!DOCTYPE html>
<html ${htmlAttrs}>
  <head>
   ${head}
    <link rel="preconnect" href="https://app.snipcart.com" />
    <link rel="preconnect" href="https://cdn.snipcart.com" />
    <link
      rel="stylesheet"
      href="https://cdn.snipcart.com/themes/v3.0.18/default/snipcart.css"
    />
  </head>
  <body ${bodyAttrs}>
    ${app} ${scripts}
    <div
      id="snipcart"
      data-api-key="YOUR_PUBLIC_API_KEY"
      hidden
    ></div>
    <script src="https://cdn.snipcart.com/themes/v3.0.18/default/snipcart.js"></script>
  </body>
</html>

Let's now add your store checkout in your website's header.

4. Add Snipcart checkout and user profile in your website's header

4.1 Add Snipcart checkout

Open the Default.vue file in the layout folder. Somewhere in the component, add the following code:

<div hidden id="snipcart" data-api-key="YOUR_PUBLIC_API_KEY"></div>

Since the default component is a layout, it will be included in all the website pages. Hence the public API key will be available throughout your website. That will make Snipcart available on all pages.

You can now add the checkout component with the following code:

<g-link class="snipcart-checkout">
  <CartLogo />
</g-link>

<CartLogo /> is specific to this demo. It simply is a Vue component containing an SVG icon. You can replace it with any text or image that suits your need.

Now, test if Snipcart checkout opens when you click on the link you just added.

Let's also display the number of items currently in the cart and their total price. Nested in your button element, add span elements with, respectively, the snipcart-total-items and snipcart-total-price classes binded:

<g-link class="snipcart-checkout">
  <CartLogo />
  <span class="snipcart-total-items"></span>
  <span class="snipcart-total-price"></span>
</g-link>

To update your cart's summary total price when the Default component re-renders, add a totalPrice attribute and a getTotalPrice method in the component script section main object in the Default page:

data: function() {
    return {
      totalPrice: 0
    };
  },
methods: {
    getTotalPrice: function() {
       return Snipcart.store.getState().cart.total;
    }
}

Then, add the following method to the object mounted attribute:

mounted: function() {
        this.totalPrice = this.getTotalPrice();
  }

Before populating the cart summary with your totalPrice data attribute, add the following custom filter in main.js. It will allow you to format the returned number as a currency:

Vue.filter("formatMoney", function (number) {
  const formatter = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
    minimumFractionDigits: 2,
  });
    
  return formatter.format(number);
});

Combine the filter with the getTotalPrice method you just created to populate the cart's summary:

<span class="snipcart-total-price">{{this.totalPrice | formatMoney }}</span>

4.2 Add Gridsome user profile

Let's add one last Snipcart feature to make customers feel at home on your site.

Add the following code in the Default.vue template:

<g-link class="snipcart-user-profile" href="#">
  <ProfileLogo />
</g-link>

Like for the Gridsome checkout, you can replace the <ProfileLogo /> component with any text or image you find that best suits your need.

Now that Snipcart is integrated, you are ready to add products to your store.

5. Add a mock product list

In order to integrate products into your component, you need actual product data. Let's make some.

Create a folder named services. In this folder, create a file named productApi.js.

Then, make a getProducts function that will return an array of products:

const getProducts = () => {
  return [
    {
      id: 1,
      name: "Blinky",
      images: {
        small: "blinky-small.png",
        big: "blinky-big.png",
      },
      descriptions: {
        short: "Pack leader",
        long:
          "Natural leader. Gets a speed boost when pack pellets are cleared.",
      },
      price: 1337,
      url: "/products/1",
    },
    {
      id: 2,
      name: "Inky",
      images: {
        small: "inky-small.png",
        big: "inky-big.png",
      },
      descriptions: {
       short: "Hungover but swift",
        long:
          "Had a big night yesterday, but brace yourself, he is still pretty fast.",
      },
      price: 3141,
      url: "/products/2",
    },
    {
      id: 3,
      name: "Pinky",
      images: {
        small: "pinky-small.png",
        big: "pinky-big.png",
      },
      descriptions: {
        short: "Natural ambusher",
        long: "Natural ambusher. Will cut you off. Be careful when you turn!",
      },
      price: 2718,
      url: "/products/3",
    },
    {
      id: 4,
      name: "Clyde",
      images: {
        small: "clyde-small.png",
        big: "clyde-big.png",
      },
      descriptions: {
        short: "Got chaotic moves",
        long: "moves = Math.random() * Math.floor(10000000)",
      },
      price: 1235,
      url: "/products/4",
    },
  ];
};
    
exports.getProducts = getProducts;

Ultimately, you’ll be able to change the function body to call your own product API and return the results. For now, let's add our data to Gridsome's built-in GraphQL server.

6. Add the data to a Gridsome collection

In the gridsome.server.js file, import the product API file:

const productApi = require("./src/services/productApi.js");

Then, change the api.loadSource method so that it looks like this:

api.loadSource(async (actions) => {
  const collection = actions.addCollection("Product");
  const data = await productApi.getProducts();
    
  data.forEach((element) => {
    collection.addNode({
      id: element.id,
      name: element.name,
      images: {
        big: element.images.big,
        small: element.images.small,
      },
      descriptions: {
        short: element.descriptions.short,
        long: element.descriptions.long,
      },
      price: element.price,
      url: element.url,
    });
  });
});

Let's look at what’s happening here: after creating a new Product collection and assigning mock products to the data variable, we loop through these products, adding a newly created node to our product collection with mapped attributes of each product.

The collection should now be available through our local Gridsome GraphQL server. It's time to display products!

7. Make your store's product page

In the templates folder, create a Product.vue file. This file will be your product template. In Gridsome, templates are used to create single pages for nodes in a collection.

8. Configure the template routing

In the gridsome.config.js file, configure the template routing by adding a templates attribute to the file's module.exports:

module.exports = {
  templates: {
    Product: "/products/:id",
  },
};

8.1 Get the needed product data from GraphQL server

To get the data, add the following code in the Products.vue page:

<page-query>
  query ($id: ID!) {
  product(id: $id) {
    id
    name
    images {
        big
    }
    descriptions {
        long
    }
    price
    url
  }
}
</page-query>

As the page-query element name implies, it will make the Products page query for the requested data in your store's build-in GraphQL server.

8.2 Add HTML elements and product data to your template

You can finally work on your product page display. Still in Product.vue, add the following code to the page:

<div>
  <g-image :src="getImageUrl(this.$page.product.images.big)" />
  <h1>{{this.$page.product.name}}</h1>
  <p>{{this.$page.product.descriptions.long}}</p>
  <p>${{this.$page.product.price | formatMoney}}</p>
</div>

And between the page <script> tags, add the following method. It will circumvent a webpack issue and allow the site to fetch your products' image URLs:

getImageUrl(url) {
      const imageFolderContext = require.context("@/PATH/TO/YOUR/IMAGE", false);
      return imageFolderContext("./" + url);
    }

8.3 Add your product attributes to the buy button

Wherever you want in the template, add a button with the class snipcart-add-item and the following product attributes. Snipcart will use these to process your product.

<button
  class="snipcart-add-item"
  :data-item-id="this.$page.product.id"
  :data-item-description="this.$page.product.descriptions.long"
  :data-item-image="getImageUrl(this.$page.product.images.big)"
  :data-item-price="this.$page.product.price"
  :data-item-name="this.$page.product.name"
  :data-item-url="this.$page.product.url"
>
  Add to cart
</button>

Your product page is now done! Let's now make a product listing page so that your customers may see all of them at once.

9. Make a product listing page

First, make a Products.vue file in the pages folder.

Then, in the components folder, create the two following files:

  • Card.vue
  • Cards.vue

You will use them in order to display your products inside cards.

9.1 Make the Card component

Inside the Card.vue file, add props to the component with the following code:

export default {
  methods: {
    props: {
        id: String,
        imageUrl: String,
        title: String,
        description: String,
        price: Number,
        linkToProductPage: String,
        url: String
    }
};

After that, add the following code to display the product info:

<template>
  <div class="card">
    <g-image :src="getImageUrl(this.imageUrl)" />
    <h3>{{this.title}}</h3>
    <p>{{ this.description }}</p>
    <p>${{ this.price | formatMoney }}</p>
    <button
      class="snipcart-add-item"
      :data-item-id="this.id"
      :data-item-description="this.description"
      :data-item-image="getImageUrl(this.imageUrl)"
      :data-item-price="this.price"
      :data-item-name="this.title"
      :data-item-url="this.url"
    >
      Add to cart
    </button>
  </div>
</template>

Finally, link the product card to the corresponding product page:

<g-link :to="this.linkToProductPage">
  <div class="card">
    <g-image :src="getImageUrl(this.imageUrl)" />
    <h3>{{this.title}}</h3>
    <p>{{ this.description }}</p>
    <p>${{ this.price }}</p>
    <button
      class="snipcart-add-item"
      :data-item-id="this.id"
      :data-item-description="this.description"
      :data-item-image="getImageUrl(this.imageUrl)"
      :data-item-price="this.price"
      :data-item-name="this.title"
      :data-item-url="this.url"
    >
      Add to cart
    </button>
  </div>
</g-link>

9.2 Make the Cards component

As you have done in your product page, query the product data by using the following code:

<static-query>
  {
    allProduct(sortBy: "id", order: ASC) { 
      edges {
      node {
        id
        name
        images {
          small
        }
        descriptions {
          short
        }
        price
        url
      }
    } 
    }
}
</static-query>

Note that compared to the product page, the query is slightly different. Long story short, that's because while the Cards component is a standard Vue component, Product is a template component. You’ll need to keep that in mind when invoking your query in our Cards component, which you’ll now do.

First, import the Card component:

<script>
  import Card from "~/components/Card.vue";
    
  export default {
    components: {
      Card,
    },
  };
</script>

Then, add the following code to invoke the product data:

<template>
  <div class="grid">
    <Card
      v-for="edge in $static.allProduct.edges"
      :key="edge.node.id"
      :id="edge.node.id"
      :imageUrl="edge.node.images.small"
      :title="edge.node.name"
      :description="edge.node.descriptions.short"
      :linkToProductPage="`/products/` + edge.node.id + `/`"
      :price="edge.node.price"
      :url="edge.node.url"
    />
  </div>
</template>

And your Card component is now done. All that's missing to finish your product listing is to add the Cards component to our Products page.

9.3 Add your Cards component to the Products page

To add your Cards component to the Products page, add the following code. It will import the component in Products.vue:

<script>
  import Cards from "~/components/Cards.vue";
    
  export default {
    components: {
      Cards,
    },
    metaInfo: {
      title: "Products",
    },
  };
</script>

Then, just add your Cards component along with some copywriting:

<template>
  <Layout>
    <div>
      <h1>Our ghosts</h1>
      <p>
        Like your everlasting anxieties, our ghosts are there to keep you
        company through the good times and bad times alike.
      </p>
      <Cards />
    </div>
  </Layout>
</template>

And voilà!

Our store can now be opened for business. Of course, you could customize it all you want. For instance, I added a homepage and some styling. If you want to check that out, see the demo below.

Live demo & GitHub repo

gridsome-ecommerce-demo

Try the live demo here

See GitHub repo here

Closing thoughts

I enjoyed Gridsome's overall simplicity. For example, its file-based routing and page and template components save a lot of boilerplate code. I also liked that it offers lazy loading and link prefetching out-of-the-box with its g-image and g-link components. Great features to improve application performance!

GraphQL proved to be a bit of a challenge: as a first time user, I had to dig a little to build the right queries to Gridsome's built-in server. However, I can see how it makes importing data to components a breeze once you get the hang of it.

I spent about three days to put this integration together. Those included a lot of tweaking to get the design right.

Besides e-commerce projects, I can see how a blog/media project would be a cool fit for Gridsome, having the right combination of architecture simplicity and static site performance. Maybe for another time!

If you've enjoyed this post, please take a second to share it on Twitter. Got comments, questions? Hit the section below!

Suggested posts: