PWA

Zero Config PWA Plugin for Next.js. This plugin is powered by workbox and other good stuff.

Features

  • Zero config for registering and generating a service worker

  • Optimized precache and runtime cache

  • Maximize lighthouse score

  • Easy to understand examples

  • Completely offline support

  • Use workbox and workbox-window v6

  • Work with cookies out of the box

  • No custom server needed for Next.js 9+

  • Handle PWA lifecycle events opt-in

  • Custom worker to run extra code in service worker with code splitting

  • Debug service worker with confidence in development mode without caching

  • Internationalization (a.k.a I18N) with next-i18next

  • Spin up a GitPod and try out examples in rocket speed

  • (Experimental) precaching .module.js when next.config.js has experimental.modern set to true

How to use

Thanks to Next.js 9+, we can use the public folder to serve static files from the root / URL path. It cuts the need to write custom server only to serve those files. Therefore the setup is easier and concise. We can use next.config.js to config next-pwa to generates service worker and workbox files into the public folder.

Step 1: Install library

yarn add next-pwa

Step 2: withPWA

Update or create next.config.js with

const withPWA = require('next-pwa')

module.exports = withPWA({
  pwa: {
    disable: process.env.NODE_ENV === 'development',
    register: true,
    scope: '/app',
    sw: 'service-worker.js',
    dest: 'public'
  }
})

Available Options

  • disable: boolean - whether to disable pwa feature as a whole

    • default to false

    • set disable: false, so that it will generate service worker in both dev and prod

    • set disable: true to completely disable PWA

    • if you don't need to debug service worker in dev, you can set disable: process.env.NODE_ENV === 'development'

  • register: boolean - whether to let this plugin register service worker for you

    • default to true

    • set to false when you want to handle register service worker yourself, this could be done in componentDidMount of your root app. you can consider the register.js as an example.

  • scope: string - url scope for pwa

    • default to /

    • set to /app so that path under /app will be PWA while others are not

  • sw: string - service worker script file name

    • default to /sw.js

    • set to another file name if you want to customize the output file name

  • runtimeCaching - caching strategies (array or callback function)

    • default: see the Runtime Caching section for the default configuration

    • accepts an array of cache entry objects, please follow the structure here

    • Note: the order of the array matters. The first rule that matches is effective. Therefore, please ALWAYS put rules with larger scope behind the rules with a smaller and specific scope.

  • publicExcludes - an array of glob pattern strings to exclude files in the public folder from being precached.

    • default: [] - this means that the default behavior will precache all the files inside your public folder

    • example: ['!img/super-large-image.jpg', '!fonts/not-used-fonts.otf']

  • buildExcludes - an array of extra pattern or function to exclude files from being precached in .next/static (or your custom build) folder

    • default: []

    • example: [/chunks\/images\/.*$/] - Don't precache files under .next/static/chunks/images (Highly recommend this to work with next-optimized-images plugin)

    • doc: Array of (string, RegExp, or function()). One or more specifiers used to exclude assets from the precache manifest. This is interpreted following the same rules as Webpack's standard exclude option.

  • subdomainPrefix: string - url prefix to allow hosting static files on a subdomain

    • default: "" - i.e. default with no prefix

    • example: /subdomain if the app is hosted on example.com/subdomain

Step 3: Add Manifest File

Create a manifest.json file in your public folder:

{
  "name": "Qiblat",
  "short_name": "Qiblat",
  "theme_color": "#F04B32",
  "background_color": "#F04B32",
  "display": "fullscreen",
  "orientation": "portrait",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "img/logo/manifest/logo-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "img/logo/manifest/logo-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}

Step 4: Add Head Meta

Add the following into _document.jsx or _document.tsx, in <Head>:

<meta name='application-name' content='PWA App' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='default' />
<meta name='apple-mobile-web-app-title' content='PWA App' />
<meta name='description' content='Best PWA App in the world' />
<meta name='format-detection' content='telephone=no' />
<meta name='mobile-web-app-capable' content='yes' />
<meta name='msapplication-config' content='/static/icons/browserconfig.xml' />
<meta name='msapplication-TileColor' content='#2B5797' />
<meta name='msapplication-tap-highlight' content='no' />
<meta name='theme-color' content='#000000' />
          
<link rel='apple-touch-icon' sizes='180x180' href='/static/icons/apple-touch-icon.png' />
<link rel='icon' type='image/png' sizes='32x32' href='/static/icons/favicon-32x32.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/static/icons/favicon-16x16.png' />
<link rel='manifest' href='/static/manifest.json' />
<link rel='mask-icon' href='/static/icons/safari-pinned-tab.svg' color='#5bbad5' />
<link rel='shortcut icon' href='/static/icons/favicon.ico' />
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:300,400,500' />
     
<meta name='twitter:card' content='summary' />
<meta name='twitter:url' content='https://yourdomain.com' />
<meta name='twitter:title' content='PWA App' />
<meta name='twitter:description' content='Best PWA App in the world' />
<meta name='twitter:image' content='https://yourdomain.com/static/icons/android-chrome-192x192.png' />
<meta name='twitter:creator' content='@DavidWShadow' />
<meta property='og:type' content='website' />
<meta property='og:title' content='PWA App' />
<meta property='og:description' content='Best PWA App in the world' />
<meta property='og:site_name' content='PWA App' />
<meta property='og:url' content='https://yourdomain.com' />
<meta property='og:image' content='https://yourdomain.com/static/icons/apple-touch-icon.png' />

Tip: Put the viewport head meta tag into _app.js rather than in _document.js if you need it.

<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover' />

Last updated

Was this helpful?