In this article, we will show how to add PWA to Next.js App.
next-pwa is a plugin for Next.js that allows you to create a progressive web app with it.
With it, building a PWA is simply painless, just drop withPWA into your next.config.js and you are already good to go!
It is also really configurable as it uses Workbox under the hood to build the service worker.
npm i @ducanh2912/next-pwa && npm i -D webpack
Update or create your next.config.js with
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
});
module.exports = withPWA({
// Your Next.js config
});
If your deployment platform requires your production image's size to not exceed a certain limit, you can also install @ducanh2912/next-pwa as one of your devDependencies and do this:
const {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_BUILD,
} = require("next/constants");
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = (phase) => {
if (phase === PHASE_DEVELOPMENT_SERVER || phase === PHASE_PRODUCTION_BUILD) {
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
});
return withPWA(nextConfig);
}
return nextConfig;
};
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 >= 9, then skip the section below.
Otherwise, you'll need to pick one of the two options below before continuing.
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.
example server:
const { createServer } = require("http");
const { join } = require("path");
const { parse } = require("url");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const hostname = "localhost";
const port = 3000;
const app = next({ dev, hostname, port });
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(port, () => {
console.log(`> Ready on http://${hostname}:${port}`);
});
});
Create a manifest.json file in your public folder:
{
"name": "My awesome PWA app",
"short_name": "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": "/",
"display": "standalone",
"orientation": "portrait"
}
import type { Metadata, Viewport } from "next";
const APP_NAME = "PWA App";
const APP_DEFAULT_TITLE = "My Awesome PWA App";
const APP_TITLE_TEMPLATE = "%s - PWA App";
const APP_DESCRIPTION = "Best PWA app in the world!";
export const metadata: Metadata = {
applicationName: APP_NAME,
title: {
default: APP_DEFAULT_TITLE,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
manifest: "/manifest.json",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: APP_DEFAULT_TITLE,
// startUpImage: [],
},
formatDetection: {
telephone: false,
},
openGraph: {
type: "website",
siteName: APP_NAME,
title: {
default: APP_DEFAULT_TITLE,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
},
twitter: {
card: "summary",
title: {
default: APP_DEFAULT_TITLE,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
},
};
export const viewport: Viewport = {
themeColor: "#FFFFFF",
};
finally if was useful add comments and share