The Gatsby PWA Fundamental Flaw and a Way Around It

January 15, 2022 3 minutes
The Gatsby PWA Fundamental Flaw and a Way Around It

How is a Gatsby site a Progressive Web App?

From the official GatsbyJs website:

Gatsby is designed to provide top-notch performance out of the box. It handles code splitting, code minification, and optimizations like pre-loading in the background, image processing, etc., so that the site you build is highly performant, without any kind of manual tuning. These performance features are a big part of supporting the progressive web app approach.

Gatsby Plugins you need to turn it into a PWA.

GatsbyJS comes with a plugin named gatsby-plugin-offline that works hand-in-hand with the gatsby-plugin-manifest. You need those two installed, and properly set up, plus your site running under https so your Progressive Web App to be installable.

Tip: To test and debug the PWA in your local machine you can build the project and the run serve. Specifically, run gatsby build && gatsby serve and open your console to see that the workbox output is being rendered there.

The problem

It’s no secret that this website runs with the magic of Gatsby and when I figured out it is designed to work as a Progressive Web App, I added the necessary plugins with no hesitation. But, what I figured when I decided to write another post, is that Gatsby’s PWA implementation is fundamentally broken by default, as you need to hard refresh the website to get the new content, every time it’s being re-rendered. Bummer…

What developers are doing to work around this issue is to try to detect when a new version of the service worker is created (hint: the version changes in every incremental build) and reload the page, either automatically or asking for permission with a prompt and then reloading the page.

A proposed solution

So after searching around the internet, for what seemed to be an eon, I resorted to a solution mostly as described above. That means I’m also checking for a service worker version change, doing an update, displaying a toast message, and after 5 seconds, hard-refreshing the site after informing the user about my “intentions”.

The actual implementation

In your gatsby-browser.js file add the following:

export const onRouteUpdate = () => {
  navigator.serviceWorker.register('/sw.js').then((reg) => {
    reg.update();
  });
};

export const onServiceWorkerUpdateReady = () => {
  console.info('PWA update available.');
  document.getElementById('___gatsby').setAttribute('data-update-available', 'true');
};

Create a component that intercepts the change of the data attribute of the root of your Gatsby site (flagged by default with the id “___gatsby”) and show the toast.

In my particular implementation I used the popular React plugin react-toastify, so go ahead and install it in your package if you want to follow along.

This is what my component looks like:


import 'react-toastify/dist/ReactToastify.min.css';
import React, { useState, useEffect } from 'react';
import { toast, ToastContainer } from 'react-toastify';

const UpdateAvailableNotice = () => {

  const [isUpdateAvailable, setIsUpdateAvailable] = useState(false)

  const notify = () => toast.info("Our website just got a bit better. We are reloading the site to ensure a smooth experience for you.", {
    onClose: () => window.location.reload(true)
  });


  useEffect(() => {
    const interval = setInterval(() => {
      const isUpdateAvailable =
        document.getElementById('___gatsby').dataset.updateAvailable === 'true';

      if (isUpdateAvailable) {
        setIsUpdateAvailable(true)
      }
    }, 1000);
    return () => {
      clearInterval(interval);
    };

  }, []);

  useEffect(() => {
    if (isUpdateAvailable) {
      notify();
    }
  }, [isUpdateAvailable]);

  return (
    <ToastContainer
      position="top-center"
      autoClose={5000}
      hideProgressBar={false}
      newestOnTop={false}
      closeOnClick={false}
      rtl={false}
      pauseOnFocusLoss
      draggable
      pauseOnHoverrole="alert"
      role="alert"
    />
  )
}

export default UpdateAvailableNotice

Now you need to “hook it” in your Layout somewhere around the root of the DOM. This is what my Layout’s return looks like:

....

return (
    <>
      <GlobalStyle />
      <UpdateAvailableNotice />
      <MainContent>
        <ContainerLayout>
          <Navbar siteTitle={data.site.siteMetadata.siteLogoText} />
        </ContainerLayout>
        <ContainerLayout>
          <main>{children}</main>
        </ContainerLayout>
      </MainContent>
      <Footer />
    </>
  )

....

Conclusion

This implementation is far from perfect, but a decent workaround for now, to a fundamental problem that comes with the much-advertised PWA capabilities of Gatsby.

Give me a shout!

If you’ve used it yourself or have another neat solution, please give me a shout!

Thank you and happy coding.