How to Create a PWA in Your Next.js App

A progressive web application PWA is an app you can install on many platforms, for example on your desktop computer or on your mobile device. It's built using web platform technologies and will provide user experience like that of your platform-specific apps. Users can download your PWA onto their device and it will show up as an app on their platform. In this blog we will be learning how to create a PWA in your Next.js App.

Jordan Wu profile picture

Jordan Wu

7 min·Posted 

Sunset from Elizabeth Lookout, Budapest, Hungary Image.
Sunset from Elizabeth Lookout, Budapest, Hungary Image.
Table of Contents

What is Progressive Web App?

A Progressive Web App (PWA) is a web app that uses web technologies to provide platform-specific user experiences. The web is used everywhere and all devices come pre-installed with a browser. It's the most used app in the world with Google Chrome being the most popular. It's a powerful tool that users use everyday to pay their bills, read their emails, read the news, watch videos or movies, listen to music, etc. You might have realized that some of them you would want to do on your mobile phone.

Though your web app can work on both desktop computer or mobile phone. Some users would like to download your app on their mobile phone for a better user experience on their phone's platform. This is where PWA comes in. Instead of creating another application to be used on a device's platform you can enhance your web app to be a PWA. This is a viable solution depending on the needs of your web app and what platforms you want to support. For a list of platforms that supports PWA, check Progressive Web Apps Compatibility.

Core Requirements Checklist

This is a checklist of things to consider when building a PWA. Because PWAs span all devices, from mobile through desktop, the checklist is about what you need to do to make your app installable and reliable for all users, regardless of screen size or input type.

Starts fast, stays fast

Performance plays a significant role in creating a great user experience. Read How to Improve Your Website Performance

Works in any browser

PWA is a web app and it needs to be compatible with many browsers. If you want similar user experience in all browsers be sure to check their browser compatibility in Mozilla Developer Network Web Docs

Responsive to any screen size

Make sure your web app is responsive on many screen sizes. Tools you can use to help is a css framework, for me I used Tailwind CSS.

Provides a custom offline page

When users are offline, keeping them in your PWA provides a more seamless and native-like experience than dropping back to the default browser offline page. Read How to Add Service Worker to Your Next.js App

Is installable

Users who install or add apps to their home screens tend to engage with those apps more, and when the PWA is installed it can take advantage of more abilities for a better user experience.

Is fully accessible

Ensure all the application's content and interactions are understood by screen readers, usable with just a keyboard, that focus is indicated, and color contrast is strong. By making your PWA accessible, you ensure it's usable for everyone. You can learn more about Accessibility and use PageSpeed Insights to improve the accessibility of your web app.

Uses powerful capabilities where available

From push messaging, WASM, and WebGL to file system access, contact pickers, and integration with app stores. The tools to create highly capable, deeply integrated PWAs are here, allowing you to create a fully-featured user experience, previously reserved for platform apps, that your users can take with them wherever they go.

More than half of all website traffic comes from organic search. Making sure that canonical URLs exist for content and that search engines can index your site is critical for users to find your PWA. Read How to Improve SEO on Your Website

Works with any input type

Users should be able to switch between input types while using your application seamlessly, and input methods should not depend on screen size.

Provides context for permission requests

Only trigger prompts for permissions like notifications, geolocation, and credentials, after providing in-context rationale to improve chances of the user accepting the prompts.

Follows best practices for healthy code

Keeping your application up-to-date and your codebase healthy makes it easier for you to deliver new features that meet the other goals laid out in this checklist.

Web App Manifest

The web manifest is a file that includes your basic information such as the app's name, icon, and theme color; advanced preferences, such as desired orientation and app shortcuts; and catalog metadata, such as screenshots. You should have a single manifest file that is hosted on the root level. The official extension is .webmanifest.

Create Manifest File in Next.js App Router

You can create a Manifest file in two ways, static or generate. I prefer to generate with a Typescript file because it gives you type checking. Add a manifest.ts file that returns a Manifest object.

File Imageapp/manifest.ts
import { MetadataRoute } from 'next'

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Jordan Wu Website',
    short_name: 'Jordan Wu',
    description: 'Jordan Wu personal website.',
    start_url: '/',
    display: 'standalone',
    background_color: '#c03434',
    theme_color: '#c03434',
    icons: [
      {
        src: '/favicons/favicon-16x16.png',
        sizes: '16x16',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-16x16.png',
        sizes: '16x16',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-32x32.png',
        sizes: '32x32',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-32x32.png',
        sizes: '32x32',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-48x48.png',
        sizes: '48x48',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-48x48.png',
        sizes: '48x48',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-72x72.png',
        sizes: '72x72',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-72x72.png',
        sizes: '72x72',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-96x96.png',
        sizes: '96x96',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-96x96.png',
        sizes: '96x96',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-144x144.png',
        sizes: '144x144',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-144x144.png',
        sizes: '144x144',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-168x168.png',
        sizes: '168x168',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-168x168.png',
        sizes: '168x168',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/apple-touch-icon.png',
        sizes: '180x180',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/apple-touch-icon.png',
        sizes: '180x180',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/android-chrome-192x192.png',
        sizes: '192x192',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/android-chrome-192x192.png',
        sizes: '192x192',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/favicon-256x256.png',
        sizes: '256x256',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/favicon-256x256.png',
        sizes: '256x256',
        type: 'image/png',
        purpose: 'maskable',
      },
      {
        src: '/favicons/android-chrome-512x512.png',
        sizes: '512x512',
        type: 'image/png',
        purpose: 'any',
      },
      {
        src: '/favicons/android-chrome-512x512.png',
        sizes: '512x512',
        type: 'image/png',
        purpose: 'maskable',
      },
    ],
    related_applications: [
      {
        platform: 'webapp',
        url: 'https://www.jordanwu.xyz/manifest.webmanifest',
      },
    ],
  }
}

This will generate a manifest file at the root level of your web app. I included related_applications as a way to know if your PWA is already installed on the platform, check Detecting related installed apps. You would want to save your favicons in the public folder. There are many free online tools to generate favicon assets for your web app. Be sure to resize them to the most common sizes. I included both any and maskable for purpose as some PWA requires specific sizes with the value any.

|-- public
|   |-- favicons
|   |   |-- android-chrome-192x192.png
|   |   |-- android-chrome-512x512.png
|   |   |-- favicon-16x16.png
|   |   |-- favicon-32x32.png
|   |   |-- favicon-48x48.png
|   |   |-- favicon-72x72.png
|   |   |-- favicon-96x96.png
|   |   |-- favicon-144x144.png
|   |   |-- favicon-168x168.png
|   |   |-- favicon-256x256.png
|   |   |-- README
|   |-- apple-touch-icon.png
|   |-- favicon.ico

There are a few approaches to create favicon and apple-icon in Next.js. I went with the static metadata approach to generating icons HTML link tags. This gives you more flexibility if you want to show different icons depending on light or dark mode. We would need to generate the icons HTML link tags in all the pages in your web app and a good place would be in layout.tsx.

File Imageapp/layout.tsx
import type { Metadata, Viewport } from 'next'

export const viewport: Viewport = {
  themeColor: 'black',
}

export const metadata: Metadata = {
  themeColor: '#c03434',
  icons: {
    icon: {
      sizes: '48x48',
      type: 'image/x-icon',
      url: '/favicon.ico',
    },
    apple: {
      sizes: '180x180',
      type: 'image/png',
      url: '/apple-touch-icon.png',
    },
    other: [
      {
        sizes: '32x32',
        type: 'image/png',
        url: '/favicons/favicon-32x32.png',
      },
      {
        sizes: '48x48',
        type: 'image/png',
        url: '/favicons/favicon-48x48.png',
      },
      {
        sizes: '96x96',
        type: 'image/png',
        url: '/favicons/favicon-96x96.png',
      },
      {
        sizes: '144x144',
        type: 'image/png',
        url: '/favicons/favicon-144x144.png',
      },
    ],
  },
}

export default function Page() {}

This will generate the following link tags and add them to your head HTML.

<meta name="theme-color" content="#c03434" />
<link rel="icon" href="/favicon.ico" sizes="48x48" type="image/x-icon" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" type="image/png" />
<link rel="icon" href="/favicons/favicon-32x32.png" sizes="32x32" type="image/png" />
<link rel="icon" href="/favicons/favicon-48x48.png" sizes="48x48" type="image/png" />
<link rel="icon" href="/favicons/favicon-96x96.png" sizes="96x96" type="image/png" />
<link rel="icon" href="/favicons/favicon-144x144.png" sizes="144x144" type="image/png" />

Summary

This should be everything you need to make your Next.js app installable on many platforms. My use case is allowing users to install my PWA on their mobile phones for better experience and to use the web app when on desktop. If you want to learn more check out Welcome to Learn Progressive Web Apps!. Be sure to use dev tools like Web app manifest tool and Run Lighthouse in Chrome DevTools to help test and debug issues.

PWA Icon on Android Home Screen
PWA Icon on Android Home Screen
PWA Standalone App
PWA Standalone App

About the Author

Jordan Wu profile picture
Jordan is a full stack engineer with years of experience working at startups. He enjoys learning about software development and building something people want. What makes him happy is music. He is passionate about finding music and is an aspiring DJ. He wants to create his own music and in the process of finding is own sound.
Email icon image
Stay up to date

Get notified when I publish something new, and unsubscribe at any time.