TL;DR
Creating PWAs with Next.js 13 and the App Router is a valuable skill. Most tutorials are outdated, and
next-pwa
isn’t actively maintained. Instead, I recommend using@ducanh2912/next-pwa
. Here are the steps:
- Install
@ducanh2912/next-pwa
andwebpack
.- Wrap Next.js config with
withPWA
plugin- Generate icons with
pwa-asset-generator
- Add
manifest.json
and metadata toapp/layout.tsx
- Exclude certain PWA files from your repo with
.gitignore.
After learning about PWA (Progressive Web Apps) and Create a PWA, I believe it’s a valuable skill to explore creating a PWA with Next.js 13 and the app router. However, the article is limited (Most online tutorials are outdated if you want to use the app router feature^[How to Create a PWA With Next.js in 10 Minutes - YouTube], and it appears that the well-known library next-pwa
is no longer actively maintained.
The problems you might encounter when implementing a PWA using outdated tutorials often revolve around service worker compatibility or requirements such as pages
folder, as mentioned in this Reddit post. However, comments in this post also suggest trying @ducanh2912/next-pwa
, a forked version of next-pwa
that is compatible with App Router.
Here, I’d like to share the steps to successfully convert a project into a PWA using Next.js 13’s App Router. Because @ducanh2912/next-pwa
offers excellent documentation on how to convert an existing project into a PWA. You can simply follow the installation and usage instructions from the documentation. Additionally, I will share how I made it work and what to pay attention to in each step.
Steps 1. Install @ducanh2912/next-pwa
Remember to install @ducanh2912/next-pwa
in your project’s dependencies and webpack
in devDependencies using your package manager.
yarn add @ducanh2912/next-pwa && yarn add -D webpack
Step 2. Wrap the Next config with the configured withPWA
plugin
Because my project is using ESM as its module system, I will show you how to wrap the Next.js config with withPWA
in ESM.
import NextPWA from "@ducanh2912/next-pwa";
const withPWA = NextPWA({
// output directory for service worker
dest: "public",
// Whether `next-pwa` should be disabled.
disable: process.env.NODE_ENV === "development",
// automatically register the service worker for you
register: true,
});
/**
* @type {import('next').NextConfig}
**/
const moduleExports = { ... }
export default withPWA(moduleExports);
Step 3. Generate icons using pwa-asset-generator
I generate a manifest.json
file in the public
folder using PWA Manifest Generator | SimiCart (skipping the icons part). Then, I also create a temporary index.html
file in the public
folder for pwa-asset-generator
to populate with metadata, which can be later added to layout.tsx
as mentioned in step 4.
npx pwa-asset-generator
# input icon
./public/logo.svg
# output icons directory
./public/pwa-assets
# automatically fill splash screen and icon meta tags to index.html
--index ./public/index.html
# automatically update manifest file with the generated icons
--manifest ./public/manifest.json
# also generate favicon image and its meta tag
--favicon true
# increase the image quality to 80
--quality 80
# make the icon transparent
--opaque false
Step 4. Add manifest.json
and metadata to app/layout.tsx
After preparing the manifest.json
and your icons, you can now add the required metadata to app/layout.tsx
. There are a few things to note. Firstly, you can add favicon
and apple-icons
as described in the Next.js official documentation under generateMetadata - icons. Secondly, ensure that manifest.json
is linked to your app using manifest
property.
const APP_NAME = "MyPWA";
const APP_TITLE_TEMPLATE = "%s - MyPWA";
const APP_DESCRIPTION = "My awesome PWA.";
export const metadata: Metadata = {
title: APP_NAME,
description: APP_DESCRIPTION,
alternates: { canonical: BASE_URL },
metadataBase: new URL(BASE_URL),
applicationName: APP_NAME,
icons: {
icon: "/pwa-assets/favicon-196.png",
apple: "/pwa-assets/apple-icon-180.png",
},
manifest: "/manifest.json",
themeColor: "#0369a1",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: APP_TITLE_TEMPLATE,
startupImage: appleStartupImages,
},
twitter: {
card: "summary",
title: {
default: APP_NAME,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
},
};
};
You can add Apple splash screens using the startupImage
property. Here, I’ve created another file and an object, appleStartupImages
, to define all the sizes of splash screens that are generated by pwa-asset-generator
.
import { AppleImage } from "next/dist/lib/metadata/types/extra-types";
export const appleStartupImages: AppleImage[] = [
{
url: "/pwa-assets/apple-splash-2048-2732.jpg",
media:
"(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)",
},
{
url: "/pwa-assets/apple-splash-2732-2048.jpg",
media:
"(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)",
},
...
]
Step 5. Avoid committing worker, workbox files
We can prevent these automatically generated sw.js
and workbox-*.js
files to be committed to our repository by simply adding these lines to .gitignore
:
# PWA assets and service workers
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/sw.js.map
**/public/workbox-*.js.map
**/public/worker-*.js.map