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.js
public/manifest.json
public/offline.html
- A file with the server action
revalidatePWA
will also be automatically created or updated:app/actions.ts
orsrc/app/actions.ts
(if you use asrc
folder 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.mjs
If 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.ts
orsrc/app/actions.ts
with therevalidatePWA
function 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.ts
orsrc/app/actions.ts
with therevalidatePWA
function for server-side cache revalidation.
PWAProvider
— wrapper for the applicationusePWAStatus
— status hook- All utilities for cache and SW management (client-actions)
- HOC
withPWA
for 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.