PWA: Build Installable Next.js App that Works Offline
Tags: webdev, nextjs, pwa, react
Published: 2024-05-01 12:25:56

Let's build and convert a simple Nextjs website to a Progressive Web Application (PWA).

## But What is PWA?

According to MDN web docs, A progressive web app (PWA) is an app that's built using web platform technologies, but that provides a user experience like that of a platform-specific (or native) app.

This simply means that with a single code written for your website, it can be made Installable on different devices either on Android, iOS, or Windows.

This PWA technology has been adopted by big companies like GitHub, Mozilla, Forbes, and many others.

![PWA on Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5t3qgcb2mfb38w9fygy6.jpg)

You may wonder why these companies are creating an Installable website while their engineers can build (world-class) native apps. 

The simple fact is that PWA does more than just install your website as an app, it offers a lot more. Let's talk about these benefits.

## Benefits of Progressive Web App (PWA)

- It can work offline and in the background
- It is Installable so that you save money than developing native apps
- It works on different devices with just a single code base
- It is fast, faster than a traditional website or mobile app
- It automatically engages with visitors to install the app, no additional set-up is needed
- And many others

With all these benefits, it is recommended to convert a website to PWA. GitHub has a mobile app and a desktop app, but the company is still leveraging the power of Progressive Web App.

Setting up PWA for a Next.js website is simple and it takes just a few minutes. Let's get started!

## Here is what I will be using in this tutorial:

- Next.js 14.2 (App dir)
- Typescript 
- Tailwind CSS 
- Service packages

Here is [the demo](https://veerage.vercel.app/) of what we are doing in this tutorial.

Okay moving on.

You are familiar with the first 3 but maybe not with `Serwist` package. 

Check it out on GitHub:

{% github https://github.com/serwist/serwist %}

The [Serwist package](https://serwist.pages.dev/docs/next/getting-started) gives us access to convert our Nextjs website to a progressive web app without any expensive configuration, and yet still open room for further configuration if you want to take advantage of PWA to the fullest.

But the standard Serwist configuration works like magic and that is what I will do in this tutorial, you can then go on to read the package documentation for further configuration.

It is worth noting that Serwist works for other frameworks like Svelte, not just Nextjs.

## How to Convert Nextjs Website to a Progressive Web App

### Step 1: Build a Next.js app

You can use any of your existing Nextjs projects or Bootstrap a new one with `yarn create next-app` or `npx create-next-app`.

It doesn't matter if you choose Page or App directory, anyone works.

If your Nextjs app is up and running, let's go to the next step.

### Step 2: Install Serwist packages for PWA

Run the following in your terminal to install all the packages we need from Serwist.

For NPM: `npm i @serwist/next && npm i -D serwist`
YARN: `yarn add @serwist/next && yarn add -D serwist`

Before moving on, you may be wondering why Serwist?

Because it is so powerful. It helps do the heavy service worker configuration and finally, the package is maintained to date.

There are other PWA packages like `next-pwa` but I don't recommend it as the last update on the package was 2 years ago… A lot has changed two years ago.

So Serwist is the chosen one.


### Step 3: Configure your website's Manifest

Create a new file in the public or app folder called `manifest.json`. We need to put your app Metadata here.

Web app manifest or simply `manifest.json` as it's popularly called is a file that contains your website Metadata. Although most of the time, we don't care about this file but for your Nextjs PWA to work, it is a must to set up a correct `manifest.json` file.

There are many web app manifest generators out there you can use such as this one by Simicart ([click to generate yours](https://www.simicart.com/manifest-generator.html/)).

Once you're done generating the JSON file, copy the Metadata code into your manifest.json file.

Your app needs icons, don’t worry about creating one, simply download it as a Zip button. A file will be downloaded, extract it, and copy the 4 images generated for your project.

Kindly note that the icons you copy to your project must be accessible in the manifest.json file, otherwise PWA might not work.

But this is not all.

A manifest.json file must have a unique ID so the cached file from website A doesn't get mixed with website B.

Update your manifest.json with this: 

```json
// manifest.json

   "id": "your-unique-id",
    "dir": "ltr",
    "lang": "en",
    "categories": [
      "business",
      "food",
      "shopping"
    ], //app categories
    "prefer_related_applications": false,
  "iarc_rating_id": "13", // minimum age of your prospective users
```

### Step 4: Register your manifest.json file in your app

Right now, you have the correct manifest.json file but your app doesn't recognize it yet.

To register it, we just need to tell Nextjs that we have it. We'll make use of Nextjs’ Metadata 

Open your `src` folder, locate and open `app`folder, in your `layout.tsx` file, add this code just before you export the RootLayout:

```tsx
export const metadata: Metadata = {
  metadataBase: new URL(“your base url"), // use a correct url otherwise your app won’t build
  title: "title of your app",
  description: "your app description here",
  category: "website",
  generator: "Next.js", // framework used

// the big is here 
  manifest: "/manifest.json", 
}
```

For the sake of this tutorial, we are concerned about registering the `manifest.json` file. When you are free, you can further add more details in the Metadata, and check online how to set it up.

We are almost done. 

### Step 5: Set up the Service worker files

Service Worker are the backbone of PWA, unfortunately this tutorial is not about that. To learn more about Service Worker, check this helpful article.

Now your `src` create a new folder called “service-worker”, inside it, create a file called `app-worker.ts`

The name can be anything and you can't place the parent folder anywhere, for instance, in the public folder but it's better to put in the `src` or `public`.

Put this code in the `app-worker.ts` file:

```tsx

import { defaultCache } from "@serwist/next/worker";
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
import { Serwist } from "serwist";

// This declares the value of `injectionPoint` to TypeScript.
// `injectionPoint` is the string that will be replaced by the
// actual precache manifest. By default, this string is set to
// `"self.__SW_MANIFEST"`.
declare global {
  interface WorkerGlobalScope extends SerwistGlobalConfig {
    __SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
  }
}

declare const self: ServiceWorkerGlobalScope;

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  skipWaiting: true,
  clientsClaim: true,
  navigationPreload: true,
  runtimeCaching: defaultCache,
  fallbacks: {
    entries: [
      {
        url: '/offline', // the page that'll display if user goes offline
        matcher({ request }) {
          return request.destination === 'document';
        },
      },
    ],
  },
});

serwist.addEventListeners();
```

This code is directly from the `serwist` tutorial, so it is advisable to check their documentation to understand what's happening here.

Right now you’ll be seeing a TypeScript error, to fix that, go to your `tsconfig.json` add this code just below the `target: es5`:

```json
 "lib": ["dom", "dom.iterable", "esnext", "webworker"],
    "types": ["@serwist/next/typings"],
```

The error should have been fixed.

_**PS: I put my `service-worker` folder, manifest.json, and the icons in the public folder. My structure is like this:**_

![File structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iegij2slnmbwsovpt10h.PNG)

### Step 6: Reconfigure your `Next.config.js` file

The file is located in the root directory, I assume you don't have anything there, If that's the case, clear whatever is there and put this code:

```typescript
const {
  PHASE_DEVELOPMENT_SERVER,
  PHASE_PRODUCTION_BUILD,
} = require("next/constants");

/** @type {(phase: string, defaultConfig: import("next").NextConfig) => Promise<import("next").NextConfig>} */
module.exports = async (phase) => {
  /** @type {import("next").NextConfig} */

// Your current or future configuration 

  const nextConfig = {

  };

  if (phase === PHASE_DEVELOPMENT_SERVER || phase === PHASE_PRODUCTION_BUILD) {
    const withSerwist = (await import("@serwist/next")).default({
      swSrc: "src/service-worker/app-worker.ts",
      swDest: "public/sw.js",
      reloadOnOnline: true,
    });
    return withSerwist(nextConfig);
  }

  return nextConfig;
};
```

There are important things to take note of here. 

In the `const nextConfig = {}` object, you can put your current Nextjs configuration if you have any, otherwise feel free to leave the object empty.

The one that follows is checking if you're on development or you've built the project, if yes, Serwist will do its magic.

We have 3 things to also take note of in the object:

- swSrc: "src/service-worker/app-worker.ts",
- swDest: "public/sw.js",
- reloadOnOnline: true,

The first one is straightforward, that is the location of our service worker configuration we set up earlier. Again, ensure that you are accessing that path correctly.

The second one is where the generated service worker code will be located, in this case, inside the public folder.

The last one is simply asking if to reload when the user has an active internet connection. For instance, the user may be accessing your app offline and when the internet is available, setting this to `true` will try to get the website from the server not from the cached storage.


### Final step: Build and test your app

If everything is set up correctly, you can now build your project by running `yarn build` or `npm run build`

When it's successfully built, check your public folder, you should see a new file called `sw.js`, it contains some random minified code, don't edit anything.

You now have converted your Nextjs website to PWA, and users can now install it as an app.

Deploy the project to your preferred hosting provider and you're good to go.

Evidence that your PWA feature is working, when you visit your website, you should see this prompt that says “Install…” like below:

![PWA Working successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m84trc18s6tcopzxz7co.png)

if you can see that, hurrah!

Don't forget that you can still achieve more with Service Worker such as push notifications etc, try to read more about Service Worker and PWA technologies.

**_Before I leave, let me talk about common errors and solutions when converting your Nextjs website to PWA._**

## Unable to install next-pwa or serwist packages

Make sure your app is not running on your local server, if it's running press CTRL C to stop it. 

Permanently delete your `node_modules` file with SHIFT + DELETE. 

Install your app packages and dependencies again by running `yarn install` or `npm install`.

Finally, install the serwist packages again.

## Done everything correctly but PWA is not working 

It's possible that your manifest.json is not accessing the correct path for the icons. If this is the case, the safest solution is to move those icons to the public folder.

Note that you can access items in your public folder by simply starting with forward / e.g to access the image folder in the public folder, you do `/images/…`

Thank you for reading. 

Don't forget to share this article with someone if it's helpful.