Repo: apps/web, Next.js 16.3.0, App Router, default next build (Turbopack), PowerSync client already in place.
- Actively maintained Workbox fork ("swiss army knife for service workers", 1.5k stars, release cadence active).
@serwist/next9.5.12 (latest stable): peernext >=14.0.0; webpack-oriented integration (withSerwist wrapper +@serwist/webpack-plugin).@serwist/turbopack(same 9.5.x line): dedicated Turbopack integration —withSerwistin next.config, a route handlerapp/serwist/[path]/route.tsthat serves the SW, plus@serwist/turbopack/workerdefault cache strategies andSerwistProviderfor registration. Built-in/~offlinedocument fallback.- Serwist 10 (preview.14 on npm) targets Next 15+ only; if Next 16.3 surfaces breakage with the 9.5.x turbopack plugin, the 10 preview is the line to try (peer ranges must be checked then).
- Turbopack is Next 16's default bundler for dev and build — the repo uses it implicitly (no webpack override in
next.config.js).next-pwa(below) cannot work here without abandoning Turbopack.
- 5.6.0, last published 4 years ago. Built on
workbox-webpack-plugin; requires webpack build. Breaks with Turbopack; stale workbox internals. Effectively superseded — Serwist lists it as an alternative but was itself forked from the@ducanh2912/next-pwacontinuation which is archived.
- Maintainer moved on; archived; Serwist is its direct continuation. No reason to pick over Serwist.
- Static
public/sw.js+ manualnavigator.serviceWorker.register, workbox-free. - Cannot precache hashed
.next/staticassets (names unknown at authoring time) → precache onlypublic/+ start URL; everything else runtime cache (stale-while-revalidate). First offline boot after a deploy can serve stale/missing chunks. - Full control, zero deps, but reinvents workbox caching semantics, no manifest injection, no
/~offlineconvention. Fine for a pure-SPA shell; weaker for SSR'd Next.
- Manifest is orthogonal to SW choice: App Router native
app/manifest.ts(metadata route) — supported since Next 14, works in 16. Icons inpublic/+appleWebAppmetadata +themeColorviewport export.
npm i -D @serwist/turbopack esbuild serwist
next.config: export default withSerwist({ ...existing })
app/serwist/[path]/route.ts: createSerwistRoute({ swSrc: "app/sw.ts", additionalPrecacheEntries: [{ url: "/~offline", revision }] })
app/sw.ts: Serwist({ precacheEntries: self.__SW_MANIFEST, skipWaiting: true, clientsClaim: true, navigationPreload: true, runtimeCaching: defaultCache, fallbacks: { entries: [{ url: "/~offline", matcher: document }] } })
app/manifest.ts + public icons
app/layout.tsx: <SerwistProvider swUrl="/serwist/sw.js"> + metadata.appleWebApp + viewport.themeColor
app/~offline/route.tsx (or page): static offline fallback
- HTML (documents): network-first → cache (defaultCache does this; stale HTML off-the-wire is the risk, hence
/~offlinefallback). - Static chunks/CSS/fonts/images: precached from build manifest (hashed URLs → immutable, cache-first safe).
- Exclude
/api/*from runtime caching: auth routes (/api/auth/**,jose-signed cookies) and PowerSync HTTP endpoints must hit network; POST bodies are never cached by workbox defaults anyway.
- PowerSync runs as a web worker + IndexedDB; service workers do not intercept WebSocket connections or worker-scoped storage. No conflict expected.
- Its own assets (
public/@powersync/*) are plain static files → they'll be precached like anypublic/file (small; fine). - Service worker must not precache anything user-specific — this SW is shell-only; all data stays in IndexedDB behind it. Cookie-gated
proxy.tsmeans unauthenticated boot redirects to/sign-in— that page is runtime-cached like any other visited route, so a signed-out user still sees a (dead) login page offline; acceptable, UX ticket #33 owns the contract.
- Next 16.3 +
@serwist/turbopack9.5.x: peer saysnext >=14but Next 16 is new — verify in a spike before committing; fallback line is Serwist 10 preview. /~offlinefallback only helps document requests; deep-linked uncached routes while offline fall back to it (by design).- SW update flow:
skipWaiting: true+clientsClaim: truein template → new SW takes over on next load; stale-tab edge cases exist; UX ticket #33 owns the version/update story.
Serwist via @serwist/turbopack (9.5.x stable first, 10 preview as fallback), manifest via app/manifest.ts, offline page /~offline, /api excluded from caching. Cost: roughly half a day incl. icons + offline testing; 3 dev deps, ~6 small files, no build pipeline changes (Turbopack stays default). Hand-rolled only if the Serwist/Next-16 pairing breaks and dependency-averse wins — not recommended.