100% free, zero-config PWA install panels and custom push notification manager widgets. Built with React and Typescript.
Active Workspace
Add to your home screen for fast access.
All PWA features pre-handled: iOS Safari instructions, interactive install panels, and custom subscription widgets.
Auto-detects browser capabilities, renders specific prompts for iOS, Android, or desktop, and manages dismiss states locally.
A clean checkbox switch component that safely registers service workers and subscribes/unsubscribes to push notifications.
Avoid default browser prompt dialogs. Renders premium custom in-app action sheets matching modern UI/UX principles.
Automatically detects when users lose internet connection and provides non-intrusive toasts to keep them informed.
Step-by-step
Run this terminal command in your root directory to install the package dependencies.
What happens next? The library will automatically inject the components into your root layout. You will instantly see the "Install App" section at the bottom of your page, and the standard install icon will appear in the browser's address bar!
npm i pwa-notifications
Edit your custom values in the newly injected public/manifest.json.
{
"name": "My Application",
"short_name": "App",
"description": "Always Connected",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0f172a",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
In your backend, use the web-push package along with the generated VAPID keys in your .env file to send notifications to subscribed clients.
Note: The subscription object is automatically generated by the client component when a user turns notifications on. You should save this object to your database.
// 1. Install web-push on your backend: npm install web-push
const webpush = require("web-push");
// 2. Configure VAPID keys (found in your generated .env file)
webpush.setVapidDetails(
"mailto:your-email@example.com",
process.env.VAPID_PUBLIC_KEY, // Use NEXT_PUBLIC_ or VITE_ prefix if applicable
process.env.VAPID_PRIVATE_KEY
);
// 3. Send a push payload to a saved user subscription
const userSubscription = { /* retrieved from your database */ };
const payload = JSON.stringify({
title: "New Match!",
body: "Check your alerts for the latest updates."
});
webpush.sendNotification(userSubscription, payload)
.then(() => console.log("Alert sent successfully!"))
.catch(err => console.error("Error sending alert:", err));
Deep Dive
Manual usage instructions for frameworks without auto-injection, and best practices for scaling push notifications.
If you are not using Next.js, the package will still generate the required sw.js and keys, but you will need to manually hook up the UI using the client API.
import { useEffect, useState } from 'react';
import { registerServiceWorker, subscribeToPush, isPushSupported, onPWAInstallable, promptPWAInstall } from "pwa-notifications/client";
export default function App() {
const [canInstall, setCanInstall] = useState(false);
useEffect(() => {
registerServiceWorker("/sw.js");
return onPWAInstallable((installable) => setCanInstall(installable));
}, []);
const handleSubscribe = async () => {
if (!isPushSupported()) return alert("Push not supported!");
const subscription = await subscribeToPush({
vapidKey: import.meta.env.VITE_VAPID_PUBLIC_KEY
});
// Send subscription to your backend
};
return (
<div>
<button onClick={handleSubscribe}>Enable Notifications</button>
{canInstall && <button onClick={promptPWAInstall}>Install App</button>}
</div>
);
}
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { registerServiceWorker, subscribeToPush, isPushSupported, onPWAInstallable, promptPWAInstall } from "pwa-notifications/client";
const canInstall = ref(false);
let cleanupInstallable = null;
onMounted(() => {
registerServiceWorker("/sw.js");
cleanupInstallable = onPWAInstallable((installable) => {
canInstall.value = installable;
});
});
onUnmounted(() => {
if (cleanupInstallable) cleanupInstallable();
});
const handleSubscribe = async () => {
if (!isPushSupported()) return alert("Push not supported!");
const subscription = await subscribeToPush({
vapidKey: import.meta.env.VITE_VAPID_PUBLIC_KEY
});
// Send subscription to your backend
};
</script>
<template>
<div>
<button @click="handleSubscribe">Enable Notifications</button>
<button v-if="canInstall" @click="promptPWAInstall">Install App</button>
</div>
</template>
Import from pwa-notifications/server to send push notifications securely from your Node.js backend.
import { sendPushNotification } from "pwa-notifications/server";
// Example payload structure
const subscription = { /* Retrieved from database */ };
await sendPushNotification(subscription, {
title: "New Alert!",
body: "You have a new message.",
icon: "/icon-192x192.svg",
url: "/"
}, {
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY
});
For complex applications (like marketplaces or dashboard systems), developers often need real-time UI updates combined with OS-level PWA background push notifications. A generic, highly efficient architecture is the Hybrid Database/Real-Time Sync Pattern:
import { useEffect, useState } from "react";
import { registerServiceWorker, subscribeToPush } from "pwa-notifications/client";
import { onSnapshot, doc } from "firebase/firestore"; // Or your preferred websocket listener
export function useNotifications(userId: string) {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
// 1. Register PWA Service Worker
registerServiceWorker("/sw.js");
// 2. Real-time trigger: refresh UI list when counter updates
const unsubscribe = onSnapshot(doc(db, "users", userId), () => {
// Fetch notifications from DB and update UI
});
return () => unsubscribe();
}, [userId]);
return { notifications };
}
Help Center
The library automatically injects a lightweight `NetworkAlert` component. It gracefully shows an offline toast when the connection drops, and notifies users once they are back online—all without any manual setup.
No. The installation script acts as a "meta-bundler", automatically generating and injecting an optimized `sw.js` and `manifest.json` right into your project's public directory during post-install.
Both are supported! The package detects your framework upon installation and injects the exact components needed—whether they are React/Next.js components or Vue/Nuxt files.
The library automatically generates highly-secure VAPID public/private key pairs and adds them to your local environment variables. Your frontend handles the public key subscription while your backend securely fires the push events.