Turn your Next.js application into a PWA

PWA can make your app look native, faster, updatable and offline-ready. In this post, we learn how to make a PWA with a Next.js application.

·4 min read
Cover Image for Turn your Next.js application into a PWA

PWAs can give your applications a feeling of being native apps: in some ways, they can even be better.

  1. Give your application a native feeling:
    • For example, if you open the Twitter website, have you noticed that you can install it on your system just like a native app? You can do the same with your own application and let your users access your app straight from their operating systems.
  2. Prevent outdated and cached client bundles:
    • PWAs can help you prevent caching issues of outdated client-side bundles when you deploy a new version of your app: in fact, thanks to service workers, the clients running your app can get notified when you deploy a new version: you'll be able to ask your users to update the application and avoid issues such as an outdated cached Javascript bundle.

In this guide, I want to show you how to turn your existing Next.js application into a PWA.

Installing next-pwa

The package next-pwa is a zero-config package to build your application as a PWA. It's extremely simple to use, and does most of the job.

First, we want to install the package with npm:

npm i -D next-pwa

Tweaking your Next.js to make a PWA

After installing the next-pwa package, we'll need to decorate the Next.js configuration:


import withPWA from 'next-pwa';
import runtimeCaching from 'next-pwa/cache.js';
const isProduction = process.env.NODE_ENV === 'production';

const config = {
  // here goes your Next.js configuration
};

const nextConfig = withPWA({
  dest: 'public',
  disable: !isProduction,
  runtimeCaching
})(
  config
);

export default nextConfig;

The above is enough to make your application a PWA!

Ignoring build files from Git

Because of the various JS files generated during the build process, you may want to ignore these an avoid checking them in your Git repository. To do so, add the following lines yo your .gitignore file:

public/sw.js
public/workbox-*.js

Adding the manifest.json file

The manifest.json file contains the configuration for the PWA application that browsers can read, and we can create it in the public folder, so that it gets added at the root of your website.

For example, you can take the below and use it with your application's configuration.

{
  "name": "My PWA",
  "short_name": "my-pwa-app",
  "icons": [
    {
      "src": "/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/android-chrome-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "theme_color": "#FFFFFF",
  "background_color": "#FFFFFF",
  "start_url": "/dashboard",
  "display": "standalone",
  "orientation": "portrait"
}

This also needs to be referenced within the <Head> component:

<link rel="manifest" href="/manifest.json" />

Notifying clients when a new version is available

Underneath, next-pwa uses workbox, a library by Google that abstracts working with PWAs: we will use workbox to detect when a new version of the application is available.

To notify customers that a new version is updated, we will create a component that will display a popup which will prompt them to update the application.

The components below Modal and Button aren't defined, but simply replace them with your implementations.

declare global {
  interface Window {
    wb: {
      messageSkipWaiting(): void;
      register(): void;
      addEventListener(name: string, callback: () => unknown);
    }
  }
}

const PwaUpdater = () => {
  const [isOpen, setIsOpen] = useState(false);
  const onConfirmActivate = () => wb.messageSkipWaiting();

  useEffect(() => {
    wb.addEventListener('controlling', () => {
      window.location.reload();
    });

    wb.addEventListener('waiting', () => setIsOpen(true));
    wb.register();
  }, []);

  return (
    <Modal
      isOpen={isOpen}
      setIsOpen={setIsOpen}
      heading={'New version available!'}
    >
      <div>
        Hey, a new version is available! Please click below to update.
      </div>

      <Button onClick={onConfirmActivate}>Reload and update</Button>
      <Button oncClick={() => setIsOpen(false)}>Cancel</Button>
    </Modal>
  );
}

export default PwaUpdater;

Because this component uses window, we need to ensure not to render it on the server. For example, if we add this to our root app, we would do something like this:

import dynamic from 'next/dynamic';

const PwaUpdater = dynamic(() => import(`./PwaUpdater`), { ssr: false });

function App({Component, appProps}: AppProps) {
  return (
    <YourProvider>
      <PwaUpdater />
      <Component {...appProps} />
    </YourProvider>
  );
}

And that's pretty much it!


Stay informed with our latest resources for building a SaaS

Subscribe to our newsletter to receive updatesor

Read more about

Cover Image for How to sell code with Lemon Squeezy and Github

How to sell code with Lemon Squeezy and Github

·7 min read
Sell and monetize your code by giving private access to your Github repositories using Lemon Squeezy
Cover Image for Writing clean React

Writing clean React

·9 min read
Learn how to write clean React code using Typescript with this guide.
Cover Image for How to use MeiliSearch with React

How to use MeiliSearch with React

·12 min read
Learn how to use MeiliSearch in your React application with this guide. We will use Meiliseach to add a search engine for our blog posts
Cover Image for Setting environment variables in Remix

Setting environment variables in Remix

·3 min read
Learn how to set environment variables in Remix and how to ensure that they are available in the client-side code.
Cover Image for Programmatic Authentication with Supabase and Cypress

Programmatic Authentication with Supabase and Cypress

·3 min read
Testing code that requires users to be signed in can be tricky. In this post, we show you how to sign in programmatically with Supabase Authentication to improve the speed of your Cypress tests and increase their reliability.
Cover Image for Reset the Supabase Database in Cypress

Reset the Supabase Database in Cypress

·4 min read
Resetting your database during E2E tests is important to prevent flakiness. In this tutorial, we'll show you how to reset the Supabase database in Cypress E2E tests.