Package Exports
- @ducanh2912/next-pwa
Readme
Zero-config PWA plugin for Next.js
This plugin is powered by Workbox and other good stuff.
π Share your awesome PWA project π here
Features
- 0οΈβ£ Zero-config for registering and generating Service Worker out of the box
- β¨ Optimized precaching and runtime caching
- π― Maximal Lighthouse score
- π Easy-to-understand examples
- π΄ Offline support with fallbacks (example)
- π¦ Uses Workbox and workbox-window v6
- πͺ Works with cookies out of the box
- π Default range requests for audios and videos
- β No custom server needed for Next.js 9+ (example)
- π§ Handle PWA lifecycle events (opt-in - example)
- π Custom worker to run extra code with code splitting and Typescript support (example)
- π Public environment variables available in custom workers
- π Debug Service Worker in development mode without caching
- π Internationalization support (a.k.a I18N) with
next-i18nextexample - π Configurable by the same Workbox configuration options for GenerateSW and InjectManifest
- β‘ Supports blitz.js (simply add to
blitz.config.js) - π Spin up a GitPod and try out examples in rocket speed
NOTE 1 -
next-pwaversion 2.0.0+ should only work withNext.js9.1+, and static files should only be served through thepublicdirectory. This makes things simpler.NOTE 2 - If you encounter error
TypeError: Cannot read property **'javascript' of undefined**during build, please consider upgrading to Webpack 5 innext.config.js.
Install
If you are new to
Next.jsorReact.js, you may want to checkout learn Next.js or Next.js documentation first. Then start from a simple example or progressive-web-app example in Next.js's repository.
npm i @ducanh2912/next-pwa
# or
# yarn add @ducanh2912/next-pwa
# or
# pnpm add @ducanh2912/next-pwaBasic usage
Step 1: withPWA
Update or create next.config.js with
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
});
module.exports = withPWA({
// Next.js config
});or if you prefer ESM:
import withPWAInit from "@ducanh2912/next-pwa";
const withPWA = withPWAInit({
dest: "public",
});
export default withPWA({
// Next.js config
});After running next build, this will generate two files in your public directory: workbox-*.js and sw.js, which will automatically be served statically.
If you are using Next.js version 9 or newer, then skip the options below and move onto Step 2.
Otherwise, you'll need to pick one of the two options below before continuing to Step 2.
Option 1: Hosting static files
Copy files to your static file hosting server, so that they are accessible from the following paths: https://yourdomain.com/sw.js and https://yourdomain.com/workbox-*.js.
An example is using Firebase hosting service to host those files statically. You can automate the copying step with scripts in your deployment workflow.
For security reasons, you must host these files directly from your domain. If the content is delivered using a redirect, the browser will refuse to run the Service Worker.
Option 2: Using a custom server
When an HTTP request is received, check if those files are requested, then return them.
Example server.js
const { createServer } = require("http");
const { join } = require("path");
const { parse } = require("url");
const next = require("next");
const app = next({ dev: process.env.NODE_ENV !== "production" });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname } = parsedUrl;
if (
pathname === "/sw.js" ||
/^\/(workbox|worker|fallback)-\w+\.js$/.test(pathname)
) {
const filePath = join(__dirname, ".next", pathname);
app.serveStatic(req, res, filePath);
} else {
handle(req, res, parsedUrl);
}
}).listen(3000, () => {
console.log(`> Ready on http://localhost:${3000}`);
});
});The following setup has nothing to do with
next-pwaplugin, and you have probably already set them up. If not, go ahead and do so.
Step 2: Add a manifest.json file
Create a manifest.json file in your public folder:
{
"name": "PWA App",
"short_name": "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": "/",
"display": "standalone",
"orientation": "portrait"
}Step 3: Add <meta />s and <link />s to your <head /> (Example)
Add the following code to your _document.tsx or _app.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="/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" href="/icons/touch-icon-iphone.png" />
<link
rel="apple-touch-icon"
sizes="152x152"
href="/icons/touch-icon-ipad.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/icons/touch-icon-iphone-retina.png"
/>
<link
rel="apple-touch-icon"
sizes="167x167"
href="/icons/touch-icon-ipad-retina.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/icons/favicon-16x16.png"
/>
<link rel="manifest" href="/manifest.json" />
<link rel="mask-icon" href="/icons/safari-pinned-tab.svg" color="#5bbad5" />
<link rel="shortcut icon" href="/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/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/icons/apple-touch-icon.png"
/>
<!-- apple splash screen images -->
<!--
<link rel='apple-touch-startup-image' href='/images/apple_splash_2048.png' sizes='2048x2732' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_1668.png' sizes='1668x2224' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_1536.png' sizes='1536x2048' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_1125.png' sizes='1125x2436' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_1242.png' sizes='1242x2208' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_750.png' sizes='750x1334' />
<link rel='apple-touch-startup-image' href='/images/apple_splash_640.png' sizes='640x1136' />
-->Tip: Put the
viewportmeta tag in your_app.tsx, rather than_document.tsx, 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"
/>Offline fallbacks
Offline fallbacks are useful when fetching from both cache and network falls, as a precached resource is served rather than an error.
To get started simply add a /_offline.tsx in your pages/. You are all set! When the user is offline, all pages which are not cached will fallback to '/_offline'.
Use this example to see it in action
next-pwa helps you precache those resources on first load, then inject a fallback handler to handlerDidError plugin to all runtimeCaching configs, so that precached resources are served when fetching fails.
You can also setup precacheFallback.fallbackURL in your runtimeCaching config entry to implement a similar functionality. The difference is that the above method is based on the resource type, whereas this method is based on matched URL pattern. If this config is set in the runtimeCaching config entry, resource-type-based fallback will be disabled automatically for this particular URL pattern to avoid conflict.
Configuration
There are options you can use to customize the behavior of this plugin:
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
// disable: process.env.NODE_ENV === 'development',
// register: true,
// scope: '/app',
// sw: 'service-worker.js',
//...
});
module.exports = withPWA({
// Next.js config
});or if you prefer ESM:
import withPWAInit from "@ducanh2912/next-pwa";
const withPWA = withPWAInit({
dest: "public",
// disable: process.env.NODE_ENV === 'development',
// register: true,
// scope: '/app',
// sw: 'service-worker.js',
//...
});
export default withPWA({
// Next.js config
});Available options
See PluginOptions
Other options
next-pwa uses workbox-webpack-plugin, other options can be found in Google's documentation for workbox-webpack-plugin for GenerateSW and InjectManifest.
Runtime caching
next-pwa has a default runtime caching array, see: cache.ts
There is a chance you may want to have your own runtime caching rules. Please feel free to copy the default cache.ts file and customize the rules as you like. Don't forget to add the configuration to your withPWAInit config in next.config.js.
Here is the document on how to write a runtime caching array, including background sync, broadcast update, and more!
Tips
- Common UX pattern to ask user to reload when a new Service Worker is installed
- Use a convention like
{command: 'doSomething', message: ''}object whenpostMessageto Service Worker. So that on the listener it can do multiple different tasks usingif...else.... - When you are debugging Service Worker, constantly clean the application cache to reduce some flaky errors.
- If you are redirecting the user to another route, please note that Workbox by default only cache response with 200 HTTP status, if you really want to cache redirected page for the route, you can specify it in
runtimeCaching(just like this:options.cacheableResponse.statuses=[200,302]). - When debugging issues, sometimes you may want to format your generated
sw.jsfile to figure out what's really going on. In development mode it is not minified though, so it is kinda readable. - You can force
next-pwato generate worker box in production mode by addingmode: 'production'in yourwithPWAInitconfig innext.config.js. Thoughnext-pwaby default generates the worker box in development mode during development (by runningnext dev) and worker box in production mode during production (by runningnext buildandnext start), you may still want to force it to build for production even in development mode of your web app because:- Reduced logging noise as the production build doesn't include logging.
- Improved performance as the production build is better optimized.
- If you just want to disable worker box's logging while still using the development build during development, simply put
self.__WB_DISABLE_DEV_LOGS = truein yourworker/index.js(create one if you don't have one). - It is common for developers to have to use the
userAgentstring to determine users' platform, and ua-parser-js library is a good friend for that.
Reference
- Google Workbox
- ServiceWorker, MessageChannel, & postMessage by NicolΓ‘s Bevacqua
- The Service Worker Lifecycle
- 6 Tips to make your iOS PWA feel like a native app
- Make Your PWA Available on Google Play Store
Fun PWA projects
- Experience SAMSUNG on an iPhone - must open on an iPhone to start
- App Scope - like an app store for PWA
- PWA Directory
- PWA Builder - Alternative way to build awesome PWA
License
MIT