A modern, lightweight push notification library for Next.js applications with full TypeScript support.
# Using npm
npm install next-push
# Using yarn
yarn add next-push
# Using pnpm
pnpm add next-push
- Install the package:
npm install next-push
- Set environment variables:
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your-public-key
VAPID_PRIVATE_KEY=your-private-key
- Create service worker file:
// /public/sw.js
self.addEventListener('install', (event) => {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim());
});
self.addEventListener('push', (event) => {
if (!event.data) {
console.log('No data received');
return;
}
try {
const data = event.data.json();
const options = {
body: data.message,
icon: data.icon || '/icon.png',
badge: '/badge.png',
data: {
url: data.url,
...data
},
requireInteraction: false,
silent: false,
tag: data.tag || 'next-push-notification'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
} catch (error) {
console.error('Error processing push notification:', error);
}
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.notification.data?.url) {
event.waitUntil(
self.clients.openWindow(event.notification.data.url)
);
}
});
self.addEventListener('notificationclose', (event) => {
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
- Wrap your app with NextPushProvider:
import { NextPushProvider } from 'next-push/client';
function App() {
return (
<NextPushProvider>
<YourApp />
</NextPushProvider>
);
}
- Use in your component:
import { useNextPushContext } from 'next-push/client';
function MyComponent() {
const { subscribe, subscribed } = useNextPushContext();
return (
<button onClick={subscribe}>
{subscribed ? 'Unsubscribe' : 'Subscribe'}
</button>
);
}
That's it! π
import { NextPushProvider, useNextPushContext } from 'next-push/client';
// Wrap your app with NextPushProvider
function App() {
return (
<NextPushProvider>
<MyComponent />
</NextPushProvider>
);
}
const MyComponent = () => {
const {
isSupported,
subscribed,
loading,
permission,
error,
subscription,
subscribe,
unsubscribe
} = useNextPushContext(); // VAPID key otomatik olarak environment variable'dan alΔ±nΔ±r
const handleSubscribe = async () => {
try {
await subscribe();
console.log('Successfully subscribed to push notifications');
console.log('Subscription:', subscription);
} catch (error) {
console.error('Subscription failed:', error);
}
};
return (
<div>
{error && (
<div style={{ color: 'red' }}>
Error: {error.message}
</div>
)}
{isSupported ? (
<div>
<p>Permission: {permission}</p>
<p>Subscribed: {subscribed ? 'Yes' : 'No'}</p>
{subscription && (
<p>Subscription: {subscription.endpoint.slice(0, 50)}...</p>
)}
{!subscribed && (
<button
onClick={handleSubscribe}
disabled={loading}
>
{loading ? 'Subscribing...' : 'Subscribe to Notifications'}
</button>
)}
{subscribed && (
<button onClick={unsubscribe}>
Unsubscribe
</button>
)}
</div>
) : (
<p>Push notifications are not supported in this browser</p>
)}
</div>
);
};
import { createServerPush } from 'next-push/server';
// Generate VAPID keys
const vapidKeys = {
publicKey: 'your-public-key',
privateKey: 'your-private-key'
};
const pushServer = createServerPush('admin@example.com', vapidKeys);
// Send notification to a single subscription
const sendNotification = async (subscription: PushSubscription) => {
const result = await pushServer.sendNotification(subscription, {
title: 'Hello!',
message: 'This is a test notification',
url: 'https://example.com',
icon: '/icon.png'
});
if (result.success) {
console.log('Notification sent successfully');
} else {
console.error('Failed to send notification:', result.error);
}
};
// Send notification to multiple subscriptions
const sendToAll = async (subscriptions: PushSubscription[]) => {
const result = await pushServer.sendNotificationToAll(subscriptions, {
title: 'Broadcast Message',
message: 'This message was sent to all subscribers',
url: 'https://example.com'
});
console.log(`Sent to ${result.successful} users, ${result.failed} failed`);
};
- π Easy Integration: Simple React hook for client-side push notifications
- π‘ Type Safety: Full TypeScript support with proper type definitions
- β‘ Lightweight: Minimal bundle size with no heavy dependencies
- π Secure: Built-in VAPID key support for secure push notifications
- π± Cross-Platform: Works across all modern browsers and devices
- π― Flexible: Customizable notification content and behavior
- π Auto-Management: Automatic service worker registration and subscription handling
- π Modular: Separate client and server modules for better tree-shaking
- π€ Simple Setup: Easy service worker setup with provided template
- π Auto VAPID: Automatic VAPID key management from environment variables
- π― Shorter API: Simplified hook names for better developer experience
- π Context Provider: Global state management with React Context
- π‘ Error Handling: Built-in error state and handling
- β‘ Auto-Subscribe: Automatic subscription when permission is granted
- π Subscription State: Access to current PushSubscription object
A React context provider that automatically sets up the service worker and provides push notification functionality.
A React hook that provides push notification functionality. Must be used within a NextPushProvider
.
config.vapidPublicKey
(string): Your VAPID public keyconfig.defaultIcon?
(string): Default icon URL for notificationsconfig.onNotificationClick?
(function): Callback when notification is clicked
isSupported
(boolean): Whether push notifications are supportedsubscribed
(boolean): Current subscription status (shorter name)loading
(boolean): Loading state for async operations (shorter name)permission
(NotificationPermission): Current notification permissionerror
(PushError | null): Current error state with detailed error typesprogress
(PushProgress): Current operation progresssubscription
(PushSubscription | null): Current push subscription objectsubscribe()
(function): Subscribe to push notificationsunsubscribe()
(function): Unsubscribe from push notificationstoggle()
(function): Toggle subscription statereset()
(function): Reset error state and progresscheck()
(function): Check current subscription status (shorter name)getSubscription()
(function): Get current subscription
interface PushConfig {
vapidPublicKey?: string; // Optional - automatically loaded from environment variables
defaultIcon?: string;
onNotificationClick?: (url?: string) => void;
logger?: (message: string, type: 'info' | 'error') => void;
retryAttempts?: number; // Number of retry attempts (default: 3)
retryDelay?: number; // Delay between retries in ms (default: 1000)
autoSubscribe?: boolean; // Automatically subscribe when permission is granted
}
interface NotificationData {
title: string;
message: string;
url?: string;
icon?: string;
}
Creates a server-side push notification handler.
mail
(string): Email address for VAPID configurationvapidKeys
(object): VAPID public and private keys
sendNotification(subscription, data)
(function): Send notification to single subscriptionsendNotificationToAll(subscriptions, data)
(function): Send notification to multiple subscriptions
# Using web-push library
npx web-push generate-vapid-keys
Next-Push requires a service worker file to handle push notifications. You need to create a service worker file in your public directory.
Create Service Worker File:
Create public/sw.js
with the following content:
// Next-Push Service Worker
self.addEventListener('install', (event) => {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim());
});
self.addEventListener('push', (event) => {
if (!event.data) {
console.log('No data received');
return;
}
try {
const data = event.data.json();
const options = {
body: data.message,
icon: data.icon || '/icon.png',
badge: '/badge.png',
data: {
url: data.url,
...data
},
requireInteraction: false,
silent: false,
tag: data.tag || 'next-push-notification'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
} catch (error) {
console.error('Error processing push notification:', error);
}
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.notification.data?.url) {
event.waitUntil(
self.clients.openWindow(event.notification.data.url)
);
}
});
self.addEventListener('notificationclose', (event) => {
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
# Client-side (public)
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your-public-key
# Server-side (private)
VAPID_PRIVATE_KEY=your-private-key
Next-Push automatically looks for VAPID public key in this order:
- Config parameter:
useNextPush({ vapidPublicKey: '...' })
- Global variable:
window.NEXT_PUSH_VAPID_PUBLIC_KEY
- Environment variable:
NEXT_PUBLIC_VAPID_PUBLIC_KEY
// Automatic - loads from environment variable
const { subscribe } = useNextPush();
// Manual - loads from config
const { subscribe } = useNextPush({
vapidPublicKey: 'your-key-here'
});
If you're upgrading from an older version:
// Old way
const { isSubscribed, isLoading, checkSubscription } = useNextPush({
vapidPublicKey: 'required-key'
});
// New way
const { subscribed, loading, check } = useNextPush(); // VAPID key automatic
// pages/index.tsx
import { useNextPush } from 'next-push/client';
export default function Home() {
const { isSupported, subscribed, subscription, subscribe, unsubscribe } = useNextPush(); // VAPID key automatic
return (
<div>
<h1>Push Notification Demo</h1>
{isSupported && (
<div>
<button onClick={subscribed ? unsubscribe : subscribe}>
{subscribed ? 'Unsubscribe' : 'Subscribe'}
</button>
{subscription && (
<p>Subscription active: {subscription.endpoint.slice(0, 50)}...</p>
)}
</div>
)}
</div>
);
}
// _app.tsx
import { NextPushProvider } from 'next-push/client';
export default function App({ Component, pageProps }) {
return (
<NextPushProvider>
<Component {...pageProps} />
</NextPushProvider>
);
}
// Any component
import { useNextPushContext } from 'next-push/client';
const MyComponent = () => {
const { subscribe, subscribed, subscription } = useNextPushContext();
return (
<div>
<button onClick={subscribe} disabled={subscribed}>
{subscribed ? 'Subscribed' : 'Subscribe'}
</button>
{subscription && (
<p>Subscription active</p>
)}
</div>
);
};
// pages/api/send-notification.ts
import { createServerPush } from 'next-push/server';
import type { NextApiRequest, NextApiResponse } from 'next';
const pushServer = createServerPush(
'admin@example.com',
{
publicKey: process.env.VAPID_PUBLIC_KEY!,
privateKey: process.env.VAPID_PRIVATE_KEY!
}
);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { subscription, notification } = req.body;
try {
const result = await pushServer.sendNotification(subscription, notification);
if (result.success) {
res.status(200).json({ message: 'Notification sent successfully' });
} else {
res.status(500).json({ error: result.error });
}
} catch (error) {
res.status(500).json({ error: 'Failed to send notification' });
}
}
// types/push.ts
import type { NotificationData } from 'next-push/client';
export interface CustomNotificationData extends NotificationData {
priority?: 'high' | 'normal';
tag?: string;
}
// components/PushNotification.tsx
import { useNextPush } from 'next-push/client';
import type { PushConfig } from 'next-push/client';
interface CustomPushConfig extends PushConfig {
onError?: (error: Error) => void;
}
export const PushNotification = ({ config }: { config: CustomPushConfig }) => {
const { isSupported, subscribe, subscribed } = useNextPush(config);
// Component implementation
};
import { useNextPush } from 'next-push/client';
const NotificationComponent = () => {
const { subscribe, error, progress } = useNextPush();
if (error) {
switch (error.type) {
case 'PERMISSION_DENIED':
return <div>Please enable notifications in browser settings</div>;
case 'VAPID_MISSING':
return <div>VAPID key not configured</div>;
case 'NOT_SUPPORTED':
return <div>Push notifications not supported in this browser</div>;
default:
return <div>Error: {error.message}</div>;
}
}
return (
<div>
<p>Progress: {progress}</p>
<button onClick={subscribe}>Subscribe</button>
</div>
);
};
const { subscribe } = useNextPush({
retryAttempts: 5,
retryDelay: 2000
});
// Automatically retries failed operations
await subscribe(); // Will retry up to 5 times with 2 second delays
const { subscribed } = useNextPush({
autoSubscribe: true
});
// Automatically subscribes when permission is granted
// No manual subscribe() call needed!
const { toggle, reset, subscribed } = useNextPush();
// Toggle subscription state
<button onClick={toggle}>
{subscribed ? 'Disable' : 'Enable'} Notifications
</button>
// Reset error state and progress
<button onClick={reset}>Reset</button>
next-push/
βββ client/ # Client-side module
β βββ index.ts # React hooks and types
β βββ ...
βββ server/ # Server-side module
βββ index.ts # Server utilities
βββ ...
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT Β© 2025
1. VAPID Key Error
Error: VAPID public key is required
Solution: Set NEXT_PUBLIC_VAPID_PUBLIC_KEY
environment variable or pass it in config.
2. Permission Denied
Error: Notification permission denied
Solution: User must manually enable notifications in browser settings.
3. Permission Not Granted
Error: Notification permission not granted
Solution: User declined the permission request. They can enable it later in browser settings.
4. Service Worker Registration Failed
Service Worker registration failed
Solution: Make sure you're using HTTPS in production (required for service workers).
5. Push Notifications Not Supported
Push notifications not supported
Solution: Check if browser supports service workers and push API.
6. Subscription Failed
Subscription failed
Solution: Check network connection and VAPID key configuration. The library will automatically retry.
Enable debug logging:
const { subscribe } = useNextPush({
logger: (message, type) => {
console.log(`[Next-Push ${type}]:`, message);
}
});
If you encounter any issues or have questions, please open an issue on GitHub.