Build an E-Commerce Progressive Web App with GatsbyJS

July 07, 2018 0 Comments

Build an E-Commerce Progressive Web App with GatsbyJS



In a rush? Skip to tutorial or live demo.

I tried buying a t-shirt on my phone the other day.

First, I get redirected to a URL.

Mobile site loads...

Content finally appears, along with a fullscreen pop-up:

Download our mobile app for a better shopping experience!

I tap the link. App store loads...

Bad reviews, blurred screenshots, 50 MB. Sigh

I close both app store and browser.

This familiar, sketchy shopping experience could have been avoided.

How? With progressive web app e-commerce.

Progressive Web Applications (PWAs) have been on the rise these last years. Solid PWA examples are popping up everywhere, and for good reasons.

They encourage an inclusive, global, adaptative approach to web development. They make sense both from a user AND a business POV, as we'll see in this piece. Frameworks like React and Vue JS are increasingly used to craft PWAs.

Today, I'll show you how to use Gatsby to build a smooth PWA store.

This post will cover:

  1. A definition of PWAs
  2. A case for PWA e-commerce
  3. An overview of Gatsby.js for PWAs
  4. A detailed PWA example with steps, code repo, and live demo

Let's dive in.

What is a Progressive Web Application (PWA)?


Progressive Web Application is an umbrella term coined by Google engineers. It is rather a set of development principles than a specific technology or stack. Three significant principles encompass the approach:

1. Reliability

Through service workers, a PWA is never unresponsive to a user's request, no matter the device or network condition, including offline status.

2. Performance

With techniques like compression, pre-caching, code splitting, and progressive rendering, a PWA drastically reduces the number of "pogo-sticking" users—the ones abandoning your website when it's slow to load! The goal here is to strive for minimum time-to-interactive.

3. Engagement

Through the Web App Manifest, PWAs become easily installable on mobile home screens. As a developer, you get the best of both worlds here: no app store redirection/lousy installs AND the ability to engage mobile users with web push notifications. All of that inside a web app that feels and behaves almost identically to a native mobile app.

For a more comprehensive PWA checklist, make sure to check this Google entry.

Why use a PWA for e-commerce?

PWA E-Commerce Example Flipkart Lite

Flipkart Lite, a prime example of mobile e-commerce with a PWA

The technical advantages of progressive web apps translate into direct business benefits for e-commerce merchants:

It widens the market and use cases.

Browsing & buying is done on most devices, on any connexion—fast or slow—from anywhere. Offline capabilities can also stimulate engagement, as we'll see in our PWA example below.

It fosters a better conversion rate.

Pre-caching with service workers makes for a fast, smooth shopping experience. Out of the box PWA audits in Chrome DevTools (Google Lighthouse) allow you to spot & fix performance issues quickly. Serving content adaptively diminishes bounce rates across the board, thus boosting user engagement metrics, a prominent SEO ranking signal.

It reduces development budget.

No need to develop separate web & mobile apps. Thanks to "add to home screen" and push notification functionalities, your e-commerce PWA looks and feels just like a native mobile app.

Considering these benefits, it's no surprise big industry players are turning to progressive web app e-commerce. Here are a few good examples of e-comm. PWAs in the wild:

Go see for yourself; these mobile commerce experiences are A1.

Why use Gatsby for this PWA example?


The short answer? Because Gatsby makes it easy to put together a PWA example swiftly.

Often presented as a static site generator for React, it is in fact much more than that. It's a full, modern website framework that has become a developer favourite in the JAMstack ecosystem.

Gatsby creator Kyle Matthews, in an interview with The New Stack, explains how his brainchild has PWA baked in:

“Google does a lot of research about how to make fast websites, and PWA is sort of an umbrella term for these patterns. So with Gatsby, we just asked ourselves, why not bake these patterns, all these things that make a website fast, into a website framework?”

In this post, he explains how Gatsby acts as a metacompiler for your site, with built-in—not optional—performance optimizations.


So Gatsby has many "checked" PWA support boxes, such as:


  • Its outputted files are static HTML, making your e-commerce content easy to crawl, index, and rank for search engines. Organic traffic is a rich source of traffic for online stores--for many shops, it is their life-blood.

  • It supports service workers through this plugin and "add to home screen" functionality with this one.

For a full list of Gatsby's impressive features, read this page.

PWA Example: building a Gatsby e-commerce progressive web app

PWA Example E-Commerce App

A bit of context for this demo: humanity is knee-deep in zombie apocalypse. Internet is sporadically available, but more often than not, network is down. Our PWA's goal:

1) Offer useful survival guides available offline.
2) Offer buyable survival gear when network is up.


For this demo, I'll use the Material Starter from Ruben Harutyunyan. It comes equipped with all plugins needed to make a Gatsby PWA with sane default configurations.


  • gatsby-plugin-manifest to allow app installation on mobile devices' homescreens
  • gatsby-plugin-sharp to automatically generate images in various sizes
  • gatsby-plugin-offline to make your app available offline with a service worker

It uses markdown files as a content source and react-md to provide a component based on the Material Design mobile style guide from Google. I'll also show you a few tricks to make Snipcart gracefully handle changing network conditions.

Let's start by installing Gatsby and the starter.

  • Install the Gatsby cli if you don't have it yet:

    npm install --global gatsby-cli

  • Create the project:

    gatsby new gatsby-pwa-demo

  • Launch the development server in the project's folder:

    cd gatsby-pwa-demo

    gatsby develop


Responsive web design is far from new. Since its arrival, the web development community cumulated a lot of good practices.

First, there's the layout of your PWA site. By providing fully responsive pages, you'll give shoppers a more consistent e-commerce experience across devices. For this, you can use a library of responsive components like react-md or use your CSS skills to create it from scratch.

Where Gatsby helps you is for serving responsive and optimized images through the gatsby-plugin-sharp. It processes your images and generates different sizes that the browser chooses from in the srcset attribute. So only the most appropriate file for the current screen size is downloaded.

Although, I would advise using .svg images when possible. Web servers can easily compress that size format, and, since they're in a vector format, you only need one file that scales to any size.

Offline capabilities

Offline functionality is often the central argument for PWAs. While not the only cool feature, I can't write a post on PWAs without talking about it. And with Gatsby, it's as simple as adding gatsby-plugin-offline to your gatsby-config.js!

Under the hood, this uses sw-precache from Google, a library which generates a service worker that automatically caches your website files for offline use.

A service worker is like a background task running outside the context of the DOM without impacting the user interface. It's useful for offline mode, as it can intercept requests and have custom caching logic. With Gatsby, all HTML and JavaScript is cached and retrieved from the cache when offline.

App-like behaviour

A nice feature of Progressive Web Apps is the ability to use a web application in place of a native mobile application and have it behave in the same way. There are many opinions on the subject—whether it's a good or bad approach to imitate the look and feel of native apps. At least web technologies give you that flexibility, and the Web App Manifest allows your app to be self-contained outside the UI of a mobile browser. Pretty neat!

gatsby-plugin-manifest generates that manifest automatically. You can customize it in gatsby-config.js:

/* ... / { resolve: "gatsby-plugin-manifest", options: { name: config.siteTitle, short_name: config.siteTitle, description: config.siteDescription, start_url: config.pathPrefix, background_color: "#F5E35C", theme_color: "#bdbdbd", display: "standalone", icons: [ { src: "/logos/logo.png", sizes: "192x192", type: "image/png" }, { src: "/logos/logo.svg", sizes: "72x72 96x96 128x128 256x256" } ] } }, / ... */ 


The parameters are mostly descriptive or visual details of the app. The most interesting one is display: "standalone". That's what will make the app open as its own window, without browser navigation options.

In some circumstances, a browser can automatically prompt the user to install the app to their homescreen. You can also trigger this manually. If you have native versions of your app, there's a parameter to instruct the browser to suggest these apps instead.



Making the cart handle changing network conditions

For this PWA example's e-commerce functionalities, I'll integrate our own dev-first shopping cart platform, Snipcart, to the app.

Snipcart works quite simply: you add the required scripts to your site, then define products directly in the HTML.

One of the shortcomings of service workers is that they can only cache content from your domain. So you'll have to use another strategy for Snipcart to handle a loss of connectivity.

First, you need to load the required assets only once you have Internet access. I'll cheat a bit here and use direct DOM manipulation to add the tags to the page's head—you can adapt it whatever framework you're using!

class Snipcart extends Component { componentDidMount() { this.isSnipcartReady = false; this.cssLoading = false; this.cssLoaded = false; this.updateScripts = this.updateScripts.bind(this); window.addEventListener('online', this.updateScripts); this.updateScripts(); } updateScripts() { if(!window.navigator.onLine) { return; } if(!this.cssLoaded && !this.cssLoading) { this.loadSnipCss(); } if(!this.isJQueryLoaded()) { return this.loadjQuery().then(this.updateScripts); } if(!this.isSnipcartLoaded()) { return this.loadSnipJs().then(this.updateScripts); } } isJQueryLoaded() { return !!(typeof window.$ == "function" && window.$.fn && window.$.fn.jquery); } isSnipcartLoaded() { return !!(window.Snipcart); } loadjQuery() { return this.addElem('script', { async: true, src: "", }); } loadSnipJs() { return this.addElem('script', { async: true, id: "snipcart", src: "", "data-api-key": this.props.apiKey, }); } loadSnipCss() { this.cssLoading = true; return this.addElem('link', { async: true, type: "text/css", rel: "stylesheet", href: "", }) .then(() => this.cssLoaded = true) .finally(() => this.cssLoading = false); } addElem(tag, attrs) { return new Promise((resolve, reject) => { var el = document.createElement(tag); el.onload = resolve; el.onerror = reject; var keys = Object.keys(attrs); for(var i=0; i<keys.length; i++) { var key = keys[i]; el.setAttribute(key, attrs[key]); } document.head.appendChild(el); }); } render() { return null; } } 

Above, I check what file were loaded, and every time the browser comes back online, I go through those checks again. With a few more events, you can handle products added to the cart while Snipcart isn't yet loaded or when you are offline:

document.body.addEventListener('click', this.handleProductClick); document.addEventListener('snipcart.ready', this.snipcartReady); 

In those event handlers, we maintain a queue of products to be added.

snipcartReady() { console.log("Snipcart finished loading"); window.Snipcart.subscribe('item.adding', this.handleItemAdding); this.isSnipcartReady = true; this.dequeueProducts(); } handleProductClick(e) { if(!"snipcart-add-item") || this.isSnipcartLoaded()) { return; } var item = JSON.parse('data-snip-def')); console.log("Queuing clicked item", item); this.productsQueue.push(item); } handleItemAdding(ev, item) { if(window.navigator.onLine) { return; } ev.preventDefault(); console.log("Queuing item from snip event", item); this.productsQueue.push(item); } dequeueProducts() { window.Snipcart.api.cart.start().then(() => { console.log("Dequeueing products", this.productsQueue); if(this.productsQueue.length > 0) { window.Snipcart.api.items.add(this.productsQueue); this.productsQueue = []; } }); } 

Live demo & GitHub repo


See live demo

See code repo


Gatsby is a darn smart piece of engineering. While it does have a learning curve and tricky debugging, once you get it, it's so powerful you'll get to love it real quick.

Crafting this whole tutorial took me a few more days than our usual demos. I encountered a few unidentified GraphQL errors, and the gatsby build command once crashed midway without returning an error code. But overall, I had a fun experience working on this PWA example.

Of course, I couldn't cover all possible use cases in a single post—there are SO many awesome features under the PWA umbrella. With more time on my hands, I'd have explored the Notification and Push APIs to boost mobile app user engagement.

Here's hoping you learned a thing or two with this piece. I sure did! :)

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

Tag cloud