PWA cache provider for Next.js.
Automatically registers a service worker, caches pages and static assets, adds a PWA manifest and offline page, supports offline mode, SPA navigation, advanced dev tools, and both server and client actions for cache management.
Made with 💜 by dev.family
-
Install the package:
yarn add next-pwa-pack # or npm install next-pwa-pack -
After installation the following files will automatically appear:
public/sw.jspublic/manifest.jsonpublic/offline.html- A file with the server action
revalidatePWAwill also be automatically created or updated:app/actions.tsorsrc/app/actions.ts(if you use asrcfolder structure).
If the files did not appear, run:
node node_modules/next-pwa-pack/scripts/copy-pwa-files.mjs # or npx next-pwa-pack/scripts/copy-pwa-files.mjsIf the server action did not appear, run:
node node_modules/next-pwa-pack/scripts/copy-pwa-server-actions.mjs
This script will create or update
app/actions.tsorsrc/app/actions.tswith therevalidatePWAfunction for server-side cache revalidation. -
Wrap your app with the provider:
// _app.tsx or layout.tsx // if you need to keep the component server-side, create your own wrapper with "use client" import { PWAProvider } from "next-pwa-pack"; export default function App({ children }) { return <PWAProvider>{children}</PWAProvider>; }
-
Done! Your app now supports PWA, offline mode, and page caching.
If you use SSR/Edge middleware or want to trigger server actions (e.g., for cache revalidation on the server), use the HOC:
// /middleware.ts
import { withPWA } from "next-pwa-pack";
function originalMiddleware(request) {
// ...your logic
return response;
}
export default withPWA(originalMiddleware, {
revalidationSecret: process.env.REVALIDATION_SECRET!,
sseEndpoint: "/api/pwa/cache-events",
webhookPath: "/api/pwa/revalidate",
});
export const config = {
matcher: ["/", "/(ru|en)/:path*", "/api/pwa/:path*"],
};HOC arguments:
originalMiddleware— your middleware function (e.g., for i18n, auth, etc.)revalidationSecret— secret for authorizing revalidation requests (required so only you can access it)sseEndpoint— endpoint for SSE events (default/api/pwa/cache-events)webhookPath— endpoint for webhook revalidation (default/api/pwa/revalidate)
Important:
In config.matcher, be sure to specify the paths that should be handled by this middleware (e.g., root, localized routes, and PWA endpoints).
-
Components:
import { PWAProvider, usePWAStatus } from "next-pwa-pack";
-
HOC:
import { withPWA } from "next-pwa-pack";
-
Client actions:
Import from
next-pwa-pack/client-actions:import { clearAllCache, reloadServiceWorker, updatePageCache, unregisterServiceWorkerAndClearCache, updateSWCache, disablePWACache, enablePWACache, clearStaticCache } from "next-pwa-pack/client-actions";
clearAllCache()— clear all cachesreloadServiceWorker()— reload the service worker and the pageupdatePageCache(url?)— update the cache for a page (current by default)unregisterServiceWorkerAndClearCache()— remove the service worker and cacheupdateSWCache(urls)— update cache for multiple pages in all tabsdisablePWACache()— temporarily disable cache (until reload)enablePWACache()— re-enable cacheclearStaticCache()- clears the static resource cache
After installing the package, your project will have (or update) a file app/actions.ts or src/app/actions.ts with the function:
export async function revalidatePWA(urls: string[]) {
// ...
}Call it from server actions, server components, or API routes to trigger PWA cache revalidation by URL.
After installing the package, a basic manifest will appear in public/manifest.json. You can edit it directly, adding your own icons, colors, app name, and other parameters:
{
"name": "MyApp",
"short_name": "MyApp",
"theme_color": "#ff0000",
"background_color": "#ffffff",
"start_url": "/",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}You can specify a custom path via the swPath prop:
<PWAProvider swPath="/custom-sw.js">{children}</PWAProvider>With the devMode prop, a "PWA Dev Tools" panel appears (in the bottom left corner):
<PWAProvider devMode>{children}</PWAProvider>Panel features:
- Online/offline status
- App update button when a new version is available
- Cache clearing
- Force service worker reload
- Force cache update for the current page
- Full service worker and cache removal
- Global cache enable/disable (until page reload)
Tracks the status of PWA/Service Worker:
import { usePWAStatus } from "next-pwa-pack";
const { online, hasUpdate, swInstalled, update } = usePWAStatus();online— online/offline statushasUpdate— is an update availableswInstalled— is the service worker installedupdate()— activate the new app version
If you update data on the server (e.g., via POST/PUT/DELETE), use:
revalidateTag(Next.js)revalidatePWA(server action)- or
updateSWCache(client action)
Example:
// server action
await revalidateTag("your-tag");
await revalidatePWA(["/your-page-url"]);You can create your own API route to trigger cache revalidation by tags and/or URLs from outside (e.g., from an admin panel or another service):
// app/api/webhook/revalidate/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidateByTag, revalidatePWA } from "@/app/actions";
import { FetchTags } from "@/app/api/endpoints/backend";
export async function POST(request: NextRequest) {
try {
const { tags, secret, urls } = await request.json();
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
let successful = 0;
let failed = 0;
let tagsRevalidated = false;
let urlsRevalidated = false;
const validTags = Object.values(FetchTags);
const invalidTags =
tags?.filter((tag) => !validTags.includes(tag as any)) || [];
if (invalidTags.length > 0) {
return NextResponse.json(
{ error: `Invalid tags: ${invalidTags.join(", ")}` },
{ status: 400 }
);
}
if (tags && tags.length > 0) {
const tagResults = await Promise.allSettled(
tags.map((tag) => revalidateByTag(tag as FetchTags))
);
successful = tagResults.filter((r) => r.status === "fulfilled").length;
failed = tagResults.filter((r) => r.status === "rejected").length;
tagsRevalidated = true;
}
if (urls && urls.length > 0) {
await revalidatePWA(urls);
urlsRevalidated = true;
}
return NextResponse.json({
success: true,
message: "Cache revalidation completed",
tagsRevalidated,
urlsRevalidated,
tags: tags || [],
urls: urls || [],
successful,
failed,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error("Webhook revalidation error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}Now you can send POST requests to /api/webhook/revalidate with the required tags, URLs, and secret — and trigger client cache updates from external systems.
In public/sw.js you can specify paths that should not be cached:
const CACHE_EXCLUDE = ["/api/", "/admin"];- Service Worker caches HTML and static assets, supports TTL (10 minutes by default).
- Offline.html — the page shown when offline.
- SPA navigation — pages are cached even during client-side navigation.
- Dev Tools — allow cache and SW management directly from the UI.
- Cache disabling is valid only until the page reloads.
- Old cache is not deleted when disabled, just not used.
- Re-enable cache — with the button or by reloading the page.
- Files didn't appear?
Run:node node_modules/next-pwa-pack/scripts/copy-pwa-files.mjs
- Server action did not appear?
Run the script manually to add the server action:This script will create or updatenode node_modules/next-pwa-pack/scripts/copy-pwa-server-actions.mjs
app/actions.tsorsrc/app/actions.tswith therevalidatePWAfunction for server-side cache revalidation.
PWAProvider— wrapper for the applicationusePWAStatus— status hook- All utilities for cache and SW management (client-actions)
- HOC
withPWAfor SSR/Edge middleware - Server action
revalidatePWA(automatically added to actions.ts)
- Star our GitHub repo ⭐
- Create pull requests, submit bugs, suggest new features or documentation updates 🔧
- Read us on Medium
- Follow us on Twitter ��
- Like our page on LinkedIn 👍
If you want to participate in the development, make a Fork of the repository, make the desired changes and send a pull request. We will be glad to consider your suggestions!
This library is distributed under the MIT license.
