Jake's pick
Nextjs Pwa
Build Progressive Web Apps with Next.js: service workers, offline support, caching strategies, push notifications, install prompts, and web app manifest. Use when creating PWAs, adding offline capability, configuring service workers, implementing push notifications, handling install prompts, or optimizing PWA performance. Triggers: PWA, progressive web app, service worker, offline, cache strategy, web manifest, push notification, installable app, Serwist, next-pwa, workbox, background sync.
Install
npx skills add jakerains/AgentSkills --skill nextjs-pwaTags
nextjspwa
Install Options
Project install
npx skills add jakerains/AgentSkills --skill nextjs-pwaGlobal install
npx skills add jakerains/AgentSkills --skill nextjs-pwa -gUse once
npx skills use jakerains/AgentSkills --skill nextjs-pwaWorks With
codexclaude-codecursoropencode
Source
Skill Preview
# Next.js PWA Skill
## Quick Reference
| Task | Approach | Reference |
|------|----------|-----------|
| Add PWA to Next.js app | Serwist (recommended) | This file → Quick Start |
| Add PWA without dependencies | Manual SW | references/service-worker-manual.md |
| Configure caching | Serwist defaultCache or custom | references/caching-strategies.md |
| Add offline support | App shell + IndexedDB | references/offline-data.md |
| Push notifications | VAPID + web-push | references/push-notifications.md |
| Fix iOS issues | Safari/WebKit workarounds | references/ios-quirks.md |
| Debug SW / Lighthouse | DevTools + common fixes | references/troubleshooting.md |
| Migrate from next-pwa | Serwist migration | references/serwist-setup.md |
---
## Quick Start — Serwist (Recommended)
Serwist is the actively maintained successor to next-pwa, built for App Router.
### 1. Install
```bash
npm install @serwist/next && npm install -D serwist
```
### 2. Create `app/manifest.ts`
```ts
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "My App",
short_name: "App",
description: "My Progressive Web App",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#000000",
icons: [
{ src: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png" },
],
};
}
```
### 3. Create `app/sw.ts` (service worker)
```ts
import { defaultCache } from "@serwist/next/worker";
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
import { Serwist } from "serwist";
declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig {
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
}
}
declare const self: ServiceWorkerGlobalScope;
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: true,
clientsClaim: true,
navigationPreload: true,
runtimeCaching: defaultCache,
});
serwist.addEventListeners();
```
### 4. Update `next.config.ts`
```ts
import withSerwist from "@serwist/next";
const nextConfig = {
// your existing config
};
export default withSerwist({
swSrc: "app/sw.ts",
swDest: "public/sw.js",
disable: process.env.NODE_ENV === "development",
})(nextConfig);
```
That's it — 4 files for a working PWA. Run `next build` and test with Lighthouse.
---
## Quick Start — Manual (No Dependencies)
Use this when you want zero dependencies or are using `output: "export"`.
### 1. Create `app/manifest.ts`
Same as above.
### 2. Create `public/sw.js`
```js
const CACHE_NAME = "app-v1";
const PRECACHE_URLS = ["/", "/offline"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
);
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") {
event.respondWith(
fetch(event.request).catch(() => caches.match("/offline"))
);
return;
}
event.respondWith(
caches.match(event.request).then((cached) => cached || fetch(event.request))
);
});
```
### 3. Register SW in layout
```tsx
// app/components/ServiceWorkerRegistration.tsx
"use client";
import { useEffect } from "react";
export function ServiceWorkerRegistration() {
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
}, []);
return null;
}
```
Add `<ServiceWorkerRegistration />` to your root layout.
---
## Decision Framework
| Scenario | Recommendation |
|----------|---------------|
| App Router, wants caching out of the box | **Serwist** |
| Static export (`output: "export"`) | *