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.