Gatsby blog as PWA (Progressive Web App)
Starting with some of the benefits, installed PWAs can bring more user engagement and conversions. On the user side, it brings the possibility to read posts offline. Progressive Web App 101 post covers more details about PWAs.
Prerequisites
- bootstrapped Gatsby blog
- installed manifest (
gatsby-plugin-manifest
) and offline (gatsby-plugin-offline
) plugins
Setup
Add plugin configurations to the Gatsby configuration file. The manifest plugin should be loaded before the offline plugin.
Prepare the app icon in 512x512 pixels, and the manifest plugin will generate the icons in all the necessary dimensions. PWA usage can be logged with the UTM link in start_url
property.
Runtime caching for static resources (JavaScript, CSS, and page data JSON files) is set to network-first caching, so it retrieves the latest changes before showing them to the user. In case of issues with caching in a local environment, an offline plugin can be disabled.
// gatsby-config.jsconst plugins = [// ...{resolve: `gatsby-plugin-manifest`,options: {name: `app name`,short_name: `app name`,start_url: `/?utm_source=pwa&utm_medium=pwa&utm_campaign=pwa`,background_color: `#FFF`,theme_color: `#2F3C7E`,display: `standalone`,icon: `src/assets/icon.png`}}];if (process.env.NODE_ENV !== 'development') {plugins.push({resolve: `gatsby-plugin-offline`,options: {workboxConfig: {runtimeCaching: [{urlPattern: /(\.js$|\.css$|static\/)/,handler: `NetworkFirst`},{urlPattern: /^https?:.*\/page-data\/.*\.json/,handler: `NetworkFirst`},{urlPattern: /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,handler: `StaleWhileRevalidate`},{urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,handler: `StaleWhileRevalidate`}]}}});}module.exports = {// ...plugins};
Service worker updates can also be detected. For a better user experience, a user should approve refreshing the page before updating it to the latest version.
// gatsby-browser.jsexports.onServiceWorkerUpdateReady = () => {const shouldReload = window.confirm('This website has been updated. Reload to display the latest version?');if (shouldReload) {window.location.href = window.location.href.replace(/#.*$/, '');}};exports.onRouteUpdate = async () => {if (!navigator) {console.log('Navigator is not defined, skipping service worker registration...');return;}if (!navigator.serviceWorker) {console.log('Service worker is not supported, skipping registration...');return;}try {const registration = await navigator.serviceWorker.register('/sw.js');await registration.update();} catch (error) {console.error('Service worker registration failed', error);}};