Headless, type-safe PWA primitives for the web. Manage push notification subscriptions, service worker lifecycle, and permissions through a consistent, framework-agnostic API.
- Headless — no UI, no opinions, just the primitives
- Type-safe — built in TypeScript with strict types throughout
- Reactive state — powered by @tanstack/store for fine-grained updates
- SSR-safe — defers all browser work to
mount(), works in Next.js and other SSR setups - Framework-agnostic — use directly or pair with an adapter
@jopgood/pwa-core— framework-agnostic core@jopgood/react-pwa— React adapter with hooks and a provider
import { PWAManager } from "@jopgood/pwa-core";
const manager = new PWAManager({
serviceWorkerUrl: "/sw.js",
vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
onSubscriptionChange: (subscription) => {
// send subscription to your backend
},
});
manager.mount();
manager.requestNotificationPermission();
manager.subscribe();import { PWAManager } from "@jopgood/pwa-core";
import { PushProvider, usePushNotifications } from "@jopgood/react-pwa";
const manager = new PWAManager({
serviceWorkerUrl: "/sw.js",
vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
});
function App() {
return (
<PushProvider manager={manager}>
<PushDemo />
</PushProvider>
);
}
function PushDemo() {
const { permission, isSubscribed, subscribe, requestPermission } =
usePushNotifications();
return (
<div>
<p>Permission: {permission}</p>
<button onClick={requestPermission}>Request permission</button>
<button
onClick={subscribe}
disabled={permission !== "granted" || isSubscribed}
>
Subscribe
</button>
</div>
);
}- Vanilla TS — plain Vite + TypeScript
- Next.js — Next.js App Router
- Issues and PRs are welcome
- GitHub Discussions
- See CONTRIBUTING.md for setup