Web push notifications in React and Next.js
React apps need the same three things as any site — a service worker, a subscription and a sender. Next.js makes the server half easy with route handlers or server actions.
- Put sw.js in public/ so it’s served from your root.
- Subscribe in a client component, triggered by a button click.
- Send from a Next.js route handler or server action using the web-push package.
- Prefix only the public VAPID key with NEXT_PUBLIC_; keep the private key server-only.
1. Service worker in public/
Anything in Next.js’s public/ folder (or Vite/CRA’s public/) is served from the site root, so public/sw.js becomes /sw.js. Use the handlers from service worker push events. If your framework also generates a worker (for example a PWA plugin), merge push into it with importScripts rather than registering two.
2. A subscribe hook
"use client";
import { useState } from "react";
const KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!;
const toBytes = (b64: string) => {
const pad = "=".repeat((4 - (b64.length % 4)) % 4);
const raw = atob((b64 + pad).replace(/-/g, "+").replace(/_/g, "/"));
return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
};
export function NotifyButton() {
const [state, setState] = useState<"idle" | "on" | "denied">("idle");
async function enable() {
const reg = await navigator.serviceWorker.register("/sw.js", { updateViaCache: "none" });
if ((await Notification.requestPermission()) !== "granted") return setState("denied");
const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: toBytes(KEY) });
await fetch("/api/push", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(sub) });
setState("on");
}
return <button onClick={enable} disabled={state === "on"}>{state === "on" ? "Alerts on" : "🔔 Get alerts"}</button>;
}3. A route handler that saves and sends
// app/api/push/route.ts
import webpush from "web-push";
webpush.setVapidDetails(
"mailto:[email protected]",
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
process.env.VAPID_PRIVATE_KEY! // server-only, never NEXT_PUBLIC_
);
export async function POST(req: Request) {
const sub = await req.json();
await db.subscriptions.upsert(sub); // your database
return new Response(null, { status: 201 });
}
export async function sendToAll(msg: { title: string; body: string; url: string }) {
for (const sub of await db.subscriptions.all()) {
try { await webpush.sendNotification(sub, JSON.stringify(msg), { TTL: 3600 }); }
catch (e: any) { if ([404, 410].includes(e.statusCode)) await db.subscriptions.remove(sub.endpoint); }
}
}Route handlers using web-push must run on the Node.js runtime, not the Edge runtime, because the library relies on Node crypto. More in Node.js web push.
4. iPhone notes
- Add a manifest with
"display": "standalone"(Next.js supportsapp/manifest.ts). - On iPhone, the button only works inside the Home Screen web app; detect standalone mode and show install steps otherwise (PWA guide).
- Check
"PushManager" in windowbefore rendering the button.
Or use a script tag
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en"><body>
{children}
<Script src="https://pusharoo.com/sdk/pusharoo.js" data-site="pk_your_site_key" strategy="afterInteractive" />
</body></html>
);
}With Pusharoo’s sw.js in public/, that’s the whole integration: prompt, iPhone hint, subscriptions, campaigns and automations, free. Your own button can call window.Pusharoo.subscribe() (API).
Questions
Do I need next-pwa to send push notifications?
No. A plain service worker in public/ plus the Push API is enough. PWA plugins are optional, for offline caching and install prompts.
Can I send web push from the Next.js Edge runtime?
The web-push package needs Node.js APIs, so use the Node.js runtime for sending.
How do I test push in development?
Browsers treat http://localhost as secure, so service workers and push work locally. Use DevTools’ Push button to test the worker.
Keep reading
The Web Push API in JavaScript: a working example
Register a worker, ask on click, subscribe with VAPID, store the subscription and send — complete code.
Web push notifications with Node.js
Install web-push, generate keys, save subscriptions with Express, send with TTL and clean up dead endpoints.
Service worker push events, done right
push and notificationclick handlers, focusing open tabs, action buttons, subscription changes and pitfalls.
PWA push notifications on iOS and Android
Manifest, service worker, iPhone Home Screen requirement, Android install, badging and a setup checklist.
More in Developers: VAPID keys for web push, explained · The Web Push protocol, explained · Web push notifications in PHP · Laravel browser push notifications (no Firebase) · Declarative Web Push, explained · Firebase web push vs. standard web push · All guides
Free for life. No limits.
Unlimited subscribers, campaigns and automations. No badges. About five minutes to install.