PWA for E-Commerce: Benefits & Live Gatsby Demo

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 http://m.thatsite.com 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 (PWA) e-commerce.

Progressive Web Applications 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 want to dive deeper into this trending technology.

This post will cover:

  1. A definition of PWAs

  2. A case for PWA e-commerce

  3. An overview of available tools for PWAs

Then, I'll show you how to use Gatsby to build a smooth PWA store. Step-by-step tutorial, code repo and live demo are all included down below.

Let's do this.

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. Oh, and this Beginner's Guide to PWA is also worth a look!

Why use a PWA for e-commerce?

Some of PWA's features

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.

Also, avoiding redirections to marketplaces like the App Store reduces friction that may cause customers to abandon the shopping process.

It reduces development budget.

No need to develop separate web & mobile apps. PWAs are built like webpages, but feels like native mobile apps, thanks to "add to home screen" and push notification functionalities. Your marketing team will be pleased to still have these great opportunities to reach customers, while ditching some of the hassles of native apps.

Talking about native mobile app, your main struggle right now might be choosing between this option or a PWA for your e-commerce project. Here’s a great blog post by Prototypr.io that dissects the pros & cons of both technologies.

Considering these benefits, it's no surprise big industry players are turning to progressive web app e-commerce.

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

Here are a few good examples of e-comm. PWAs in the wild:

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

PWA e-commerce development tools

Now, there are many ways to build progressive web apps—no matter your JS framework of choice. It’s something that more & more tools are tackling as out-of-the-box functionality.

→ Vue.js

  • Gridsome — Inspired by Gatsby, which I’ll use in the live demo below.

  • Nuxt.js — Tested for real-life projects by our friends at Spektrum, with great results!

→ React

→ Angular

Unlike the two other frameworks, Angular doesn’t have tools to ease you in PWA development, but you can still refer to the documentation or tutorials to get started.

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:

Progressive rendering
  • 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 e-commerce: building a demo Gatsby progressive web 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.

Pre-requisites

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

Namely:

  • 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's a simple and lean theme that'll make for good base to build upon. I'll also show you a few tricks to make Snipcart gracefully handle changing network conditions.

Let's start by generating and starting our project:

  • Scaffold the repo:

    npx gatsby-cli new gatsby-pwa-demo https://github.com/gatsbyjs/gatsby-starter-blog

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

    cd gatsby-pwa-demo

    npm run develop

Responsiveness

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. Don't forget to use the <Image /> component with Gatsby!

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: "https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js",
        });
    }
    loadSnipJs() {
        return this.addElem('script', {
            async: true,
            id: "snipcart",
            src: "https://cdn.snipcart.com/scripts/2.0/snipcart.js",
            "data-api-key": this.props.apiKey,
        });
    }
    loadSnipCss() {
        this.cssLoading = true;
        return this.addElem('link', {
            async: true,
            type: "text/css",
            rel: "stylesheet",
            href: "https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css",
        })
        .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(!e.target.classList.contains("snipcart-add-item") || this.isSnipcartLoaded()) {
        return;
    }

    var item = JSON.parse(e.target.getAttribute('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

Conclusion

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!

About the author

Jean-Sebastien Tremblay
Developer

Jean-Sébastien has been coding for over 12 years. He's been asked to share his knowledge at various software development conferences like OpenCode, Cabane.io, and the WAQ. Jean-Sébastien masters .NET & Golang in the back, and TypeScript in the front. An Arduino hobbyist, he can also grow the manliest beard you've ever seen.

Follow him on Twitter.

Using Snipcart to Manage Your Event Tickets Sale

Read next from Jean-Sebastien
View more

36 000+ geeks are getting our monthly newsletter: join them!