Back to skills library
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.

Skill: nextjs-pwaUse case: Web developmentSource: jakerains/AgentSkillsAdded: Jun 29, 2026

Install

npx skills add jakerains/AgentSkills --skill nextjs-pwa

Tags

nextjspwa

Install Options

Project installnpx skills add jakerains/AgentSkills --skill nextjs-pwa
Global installnpx skills add jakerains/AgentSkills --skill nextjs-pwa -g
Use oncenpx skills use jakerains/AgentSkills --skill nextjs-pwa

Works 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"`) | *
    Nextjs Pwa | Skills Library | Jake Rains