# 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.

*Published: 2022-09-22*
*Canonical: https://makerkit.dev/blog/tutorials/pwa-nextjs*

---

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:

 ```js {% title="next.config.js" %}

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.

 ```json {% title="manifest.json" %}
{
  "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:

```html
<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.

 ```tsx {% title="PwaUpdater.tsx" %}
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:

 ```tsx {% title="_app.tsx" %}
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!
