From b1df3f0d40294c7dc0690d37a6d5cba8344e2b90 Mon Sep 17 00:00:00 2001 From: Marat Alekperov Date: Thu, 19 Mar 2026 23:04:01 +0100 Subject: [PATCH 1/3] docs(push): add Next.js push notifications getting started guide Co-Authored-By: Claude Sonnet 4.6 --- src/data/nav/pubsub.ts | 4 + .../docs/push/getting-started/nextjs.mdx | 480 ++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 src/pages/docs/push/getting-started/nextjs.mdx diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts index 6936a8f9ab..ab6f112ce4 100644 --- a/src/data/nav/pubsub.ts +++ b/src/data/nav/pubsub.ts @@ -268,6 +268,10 @@ export default { name: 'FCM', link: '/docs/push/getting-started/fcm', }, + { + name: 'Next.js', + link: '/docs/push/getting-started/nextjs', + }, ], }, { diff --git a/src/pages/docs/push/getting-started/nextjs.mdx b/src/pages/docs/push/getting-started/nextjs.mdx new file mode 100644 index 0000000000..29763a67b9 --- /dev/null +++ b/src/pages/docs/push/getting-started/nextjs.mdx @@ -0,0 +1,480 @@ +--- +title: "Getting started: Push Notifications in Next.js" +meta_description: "Get started with Ably Push Notifications in Next.js. Learn how to register a service worker, activate push on your client, handle incoming notifications, and send push messages from a Next.js app." +meta_keywords: "Push Notifications Next.js, Ably Push, Web Push, Service Worker, Next.js push notifications, Ably Push Notifications guide, realtime push Next.js, push notification example, Ably tutorial Next.js, device registration, push messaging" +--- + +This guide will get you started with Ably Push Notifications in a Next.js application using the App Router. + +You'll learn how to set up an Ably Realtime client with push notification support, register a service worker, activate push notifications, subscribe to channel-based push, send push notifications, and handle incoming notifications in both the service worker and the React component. + +## Prerequisites + +1. [Sign up](https://ably.com/signup) for an Ably account. +2. Create a [new app](https://ably.com/accounts/any/apps/new), and create your first API key in the **API Keys** tab of the dashboard. +3. Your API key will need the `publish` and `subscribe` capabilities. For sending push notifications from your app, you'll also need the `push-admin` capability. +4. For channel-based push, add a rule for the channel with **Push notifications enabled** checked. In the dashboard left sidebar: **Configuration** → **Rules** → **Add** or **Edit** a rule, then enable the Push notifications option. See [rules](/docs/channels#rules) for details. +5. A modern browser that supports the [Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) (Chrome, Firefox, or Edge recommended). +6. [Node.js](https://nodejs.org/) 18 or higher. + +### (Optional) Install Ably CLI + +Use the [Ably CLI](https://github.com/ably/cli) as an additional client to quickly test Pub/Sub features and push notifications. + +1. Install the Ably CLI: + + +```shell +npm install -g @ably/cli +``` + + +2. Run the following to log in to your Ably account and set the default app and API key: + + +```shell +ably login +``` + + +### Create a Next.js project + +Create a new Next.js project using the official create command: + + +```shell +npx create-next-app@latest ably-push-tutorial --typescript --app --no-tailwind --eslint --src-dir +cd ably-push-tutorial +``` + + +Then install the Ably SDK: + + +```shell +npm install ably +``` + + +## Step 1: Set up Ably + +This step initializes the Ably Realtime client with the necessary configuration for push notifications, including the API key, client ID, and service worker URL. It also subscribes to a channel to receive incoming messages. + +Because the Ably SDK runs in the browser, the component must be a [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components). Create `src/app/push/page.tsx` with the `'use client'` directive at the top: + + +```typescript +'use client'; + +import * as Ably from 'ably'; +import AblyPushPlugin from 'ably/push'; +import { useEffect, useRef, useState } from 'react'; + +const CHANNEL_NAME = 'exampleChannel1'; + +export default function PushPage() { + const realtimeRef = useRef(null); + const channelRef = useRef(null); + const [status, setStatus] = useState(''); + const [log, setLog] = useState([]); + + function appendLog(message: string) { + setLog((prev) => [...prev, message]); + } + + useEffect(() => { + const realtime = new Ably.Realtime({ + key: '{{API_KEY}}', // Your Ably API key (do not use API key in production, use token authentication instead) + clientId: 'push-tutorial-client', + plugins: { Push: AblyPushPlugin }, + pushServiceWorkerUrl: '/service-worker.js', + }); + + realtimeRef.current = realtime; + + realtime.connection.once('connected', () => { + appendLog('Connected to Ably with clientId: ' + realtime.auth.clientId); + + const channel = realtime.channels.get(CHANNEL_NAME); + channelRef.current = channel; + + channel.subscribe((message) => { + let entry = `Received message: ${message.name}`; + if (message.data) entry += ` — data: ${JSON.stringify(message.data)}`; + if (message.extras?.push) { + entry += ` — push: ${message.extras.push.notification.title} — ${message.extras.push.notification.body}`; + } + appendLog(entry); + }); + }); + + return () => { + realtime.close(); + }; + }, []); + + return ( +
+

Ably Push Notifications — Next.js

+ {status &&

{status}

} +
+ {log.map((entry, i) => ( +

{entry}

+ ))} +
+
+ ); +} +``` +
+ +Key configuration options: + +- **`key`**: Your Ably API key. +- **`clientId`**: A unique identifier for this client. +- **`plugins`**: The `AblyPushPlugin` enables push notification support. +- **`pushServiceWorkerUrl`**: Path to the service worker file. In Next.js, files in `public/` are served from the root, so `/service-worker.js` maps to `public/service-worker.js`. + +The `useEffect` hook ensures the Ably client is only created in the browser, and the cleanup function closes the connection when the component unmounts. + +## Step 2: Set up push notifications
+ +This step covers activating and deactivating push notifications. When activated, the browser prompts the user for notification permission and registers the device with Ably. + + + +Add the following functions inside `PushPage`, after the `useEffect`: + + +```typescript +async function activatePush() { + const realtime = realtimeRef.current; + if (!realtime) return; + try { + setStatus('Activating push notifications...'); + await realtime.push.activate(); + setStatus('Push activated. Device ID: ' + realtime.device().id); + appendLog('Push activated. Device ID: ' + realtime.device().id); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Failed to activate push: ' + message); + } +} + +async function deactivatePush() { + const realtime = realtimeRef.current; + if (!realtime) return; + try { + setStatus('Deactivating push notifications...'); + await realtime.push.deactivate(); + setStatus('Push notifications deactivated.'); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Failed to deactivate push: ' + message); + } +} +``` + + +When `realtime.push.activate()` is called, the Ably SDK will: + +1. Register the service worker specified in `pushServiceWorkerUrl`. +2. Request notification permission from the user. +3. Obtain a push subscription from the browser's Push API. +4. Register the device with Ably's push notification service. + +After successful activation, `realtime.device().id` contains the unique device ID assigned by Ably. + +## Step 3: Receive push notifications + +A service worker runs in the background and can receive push notifications even when the page is not open. In Next.js, the service worker file must be placed in the `public/` directory so it is served from the root path. + +### Create the service worker + +Create `public/service-worker.js`: + + +```javascript +// Handle push events +self.addEventListener('push', (event) => { + const eventData = event.data.json(); + + const notification = { + title: eventData.notification.title, + body: eventData.notification.body, + data: eventData.data, + }; + + // Display a native browser notification + self.registration.showNotification(notification.title, notification); + + // Also forward to open pages (optional, for demonstration purposes) + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + clientList.forEach((client) => { + client.postMessage({ type: 'tutorial-push', notification }); + }); + }) + ); +}); +``` + + +Key points about the service worker: + +- `event.data.json()` — parses the push message payload sent by the server. +- `self.registration.showNotification()` — displays a native browser notification. +- `client.postMessage()` — sends data to open pages of your application. +- `push` events can only be handled in a service worker, not in the main page. + +### Handle notification clicks + +Add a `notificationclick` listener in `public/service-worker.js` to handle what happens when the user clicks a notification: + + +```javascript +// Handle notification clicks +self.addEventListener('notificationclick', (event) => { + event.notification.close(); + + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + const url = event.notification.data?.url || '/push'; + + for (const client of clientList) { + if ((client.url.endsWith('/push') || url === '/push') && client.focus) { + client.postMessage({ + type: 'tutorial-push-click', + notification: { + title: event.notification.title, + body: event.notification.body, + data: event.notification.data, + }, + }); + return client.focus(); + } + } + + if (clients.openWindow) { + return clients.openWindow(url); + } + }) + ); +}); +``` + + +When a notification is clicked, the handler closes the notification, looks for an existing window, sends it the notification data via `postMessage`, and focuses it. If no window exists, it opens a new one. + +### Handle notifications in the component + +Add a second `useEffect` inside `PushPage` to receive messages from the service worker and update the log: + + +```typescript +useEffect(() => { + if (!navigator.serviceWorker) return; + + const handler = (event: MessageEvent) => { + const notification = event.data?.notification; + if (!notification) return; + switch (event.data?.type) { + case 'tutorial-push': + appendLog(`Received push: ${notification.title} — ${notification.body}`); + break; + case 'tutorial-push-click': + appendLog(`Clicked push: ${notification.title} — ${notification.body}`); + break; + } + }; + + navigator.serviceWorker.addEventListener('message', handler); + return () => navigator.serviceWorker.removeEventListener('message', handler); +}, []); +``` + + +### Subscribe to channel push + +Add the following functions to subscribe and unsubscribe the device from channel-based push: + + +```typescript +async function subscribeToChannel() { + const channel = channelRef.current; + if (!channel) return; + try { + await channel.push.subscribeDevice(); + setStatus('Subscribed to push on channel: ' + CHANNEL_NAME); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Failed to subscribe: ' + message); + } +} + +async function unsubscribeFromChannel() { + const channel = channelRef.current; + if (!channel) return; + try { + await channel.push.unsubscribeDevice(); + setStatus('Unsubscribed from push on channel: ' + CHANNEL_NAME); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Failed to unsubscribe: ' + message); + } +} +``` + + +To test push notifications using the Ably CLI, send a push directly to the client ID: + + +```shell +ably push publish --client-id push-tutorial-client \ + --title "Test push" \ + --body "Hello from CLI!" \ + --data '{"foo":"bar","baz":"qux"}' +``` + + +## Step 4: Build the UI + +Replace the `return` statement in `PushPage` with the following to wire up all the buttons: + + +```typescript +return ( +
+

Ably Push Notifications — Next.js

+ + {status && ( +

+ {status} +

+ )} + +
+ + + + + + + + +
+ +
+ {log.map((entry, i) => ( +

{entry}

+ ))} +
+
+); +``` +
+ +Start the development server: + + +```shell +npm run dev +``` + + +Navigate to `http://localhost:3000/push` in your browser. Click **Activate Push**, grant notification permission, and wait for the device ID to appear in the log. + +To test channel push, click **Subscribe to Channel**, then publish a message with a `push` `extras` field using the Ably CLI: + + +```shell +ably channels publish exampleChannel1 '{"name":"example","data":"Hello from CLI!","extras":{"push":{"notification":{"title":"Ably CLI","body":"Hello from CLI!"},"data":{"foo":"bar"}}}}' +``` + + +## Step 5: Send push with code
+ +Add the following functions inside `PushPage` to send push notifications programmatically using the Push Admin API: + + +```typescript +async function sendPushToDevice() { + const realtime = realtimeRef.current; + if (!realtime) return; + try { + const deviceId = realtime.device().id; + await realtime.push.admin.publish( + { deviceId }, + { + notification: { title: 'Push Tutorial', body: 'Hello from device ID!' }, + data: { foo: 'bar', baz: 'qux' }, + } + ); + setStatus('Sent push to device ID: ' + deviceId); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Error sending to device: ' + message); + } +} + +async function sendPushToClient() { + const realtime = realtimeRef.current; + if (!realtime) return; + try { + const clientId = realtime.auth.clientId; + await realtime.push.admin.publish( + { clientId }, + { + notification: { title: 'Push Tutorial', body: 'Hello from client ID!' }, + data: { foo: 'bar', baz: 'qux' }, + } + ); + setStatus('Sent push to client ID: ' + clientId); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Error sending to client: ' + message); + } +} + +async function sendPushToChannel() { + const channel = channelRef.current; + if (!channel) return; + try { + await channel.publish({ + name: 'example', + data: 'Hello from channel!', + extras: { + push: { + notification: { + title: 'Channel Push', + body: 'Sent via channel: ' + CHANNEL_NAME, + }, + data: { customKey: 'customValue' }, + }, + }, + }); + setStatus('Sent push to channel: ' + CHANNEL_NAME); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + setStatus('Error sending to channel: ' + message); + } +} +``` + + +The `extras.push` object has two parts: + +- `notification`: Contains `title` and `body` displayed in the browser notification. +- `data`: Custom key-value pairs delivered to the service worker but not shown directly. + +## Next steps + +* Understand [token authentication](/docs/auth/token) before going to production. +* Explore [push notification administration](/docs/push#push-admin) for managing devices and subscriptions. +* Learn about [channel rules](/docs/channels#rules) for channel-based push notifications. +* Read more about the [Push Admin API](/docs/api/realtime-sdk/push-admin). +* Check out the [Web Push Notifications](/docs/push/configure/web) documentation for advanced use cases. + +You can also explore the [Ably JavaScript SDK](https://github.com/ably/ably-js) on GitHub, or visit the [API references](/docs/api/realtime-sdk?lang=javascript) for additional functionality. From dfa5301579821273998f4dcb1f122f917b9cae53 Mon Sep 17 00:00:00 2001 From: Marat Alekperov Date: Fri, 20 Mar 2026 01:32:35 +0100 Subject: [PATCH 2/3] docs(push): align Next.js guide structure and comments with web push guide Co-Authored-By: Claude Sonnet 4.6 --- .../docs/push/getting-started/nextjs.mdx | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/pages/docs/push/getting-started/nextjs.mdx b/src/pages/docs/push/getting-started/nextjs.mdx index 29763a67b9..c051ace4c3 100644 --- a/src/pages/docs/push/getting-started/nextjs.mdx +++ b/src/pages/docs/push/getting-started/nextjs.mdx @@ -149,6 +149,7 @@ Add the following functions inside `PushPage`, after the `useEffect`: ```typescript +// Activate push notifications async function activatePush() { const realtime = realtimeRef.current; if (!realtime) return; @@ -163,6 +164,7 @@ async function activatePush() { } } +// Deactivate push notifications async function deactivatePush() { const realtime = realtimeRef.current; if (!realtime) return; @@ -201,6 +203,7 @@ Create `public/service-worker.js`: self.addEventListener('push', (event) => { const eventData = event.data.json(); + // Prepare the notification object suitable for both `showNotification` and `postMessage` const notification = { title: eventData.notification.title, body: eventData.notification.body, @@ -239,10 +242,12 @@ Add a `notificationclick` listener in `public/service-worker.js` to handle what self.addEventListener('notificationclick', (event) => { event.notification.close(); + // Open or focus the app window event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { const url = event.notification.data?.url || '/push'; + // Check if there's already a window open for (const client of clientList) { if ((client.url.endsWith('/push') || url === '/push') && client.focus) { client.postMessage({ @@ -257,6 +262,7 @@ self.addEventListener('notificationclick', (event) => { } } + // Open a new window if none exists if (clients.openWindow) { return clients.openWindow(url); } @@ -274,6 +280,7 @@ Add a second `useEffect` inside `PushPage` to receive messages from the service ```typescript +// Listen for messages from the service worker about push notifications useEffect(() => { if (!navigator.serviceWorker) return; @@ -296,39 +303,45 @@ useEffect(() => { ``` -### Subscribe to channel push +### Channel push notifications -Add the following functions to subscribe and unsubscribe the device from channel-based push: +Push notifications can be sent either directly to your `deviceId` (or `clientId`), or posted to a channel, in which case you first need to subscribe your device to that channel: ```typescript +// Subscribe to push notifications on the channel async function subscribeToChannel() { const channel = channelRef.current; if (!channel) return; try { await channel.push.subscribeDevice(); - setStatus('Subscribed to push on channel: ' + CHANNEL_NAME); + setStatus('Subscribed to push notifications on channel: ' + CHANNEL_NAME); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); - setStatus('Failed to subscribe: ' + message); + setStatus('Failed to subscribe to channel push: ' + message); } } +// Unsubscribe from push notifications on the channel async function unsubscribeFromChannel() { const channel = channelRef.current; if (!channel) return; try { await channel.push.unsubscribeDevice(); - setStatus('Unsubscribed from push on channel: ' + CHANNEL_NAME); + setStatus('Unsubscribed from push notifications on channel: ' + CHANNEL_NAME); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); - setStatus('Failed to unsubscribe: ' + message); + setStatus('Failed to unsubscribe from channel push: ' + message); } } ``` -To test push notifications using the Ably CLI, send a push directly to the client ID: +Sending push notifications using `deviceId` or `clientId` requires the `push-admin` capability for your API key. Use this method for testing purposes. In a production environment, you would typically send push notifications from your backend server (by posting messages with `push` `extras` field to a channel). + +To test push notifications in your app, you can use [Ably dashboard](https://ably.com/dashboard) or Ably CLI. + +To send to your client using Ably CLI paste the following command into your terminal: ```shell @@ -339,6 +352,8 @@ ably push publish --client-id push-tutorial-client \ ``` +For sending pushes via a channel, we need some actual UI to be able to subscribe to this channel. So, let's build one. + ## Step 4: Build the UI Replace the `return` statement in `PushPage` with the following to wire up all the buttons: @@ -360,9 +375,6 @@ return ( - - - @@ -386,7 +398,9 @@ npm run dev Navigate to `http://localhost:3000/push` in your browser. Click **Activate Push**, grant notification permission, and wait for the device ID to appear in the log. -To test channel push, click **Subscribe to Channel**, then publish a message with a `push` `extras` field using the Ably CLI: +### Send push via channel + +To test pushes via channel, subscribe to the channel in the UI and post a message to the "exampleChannel1" with a `push` `extras` field using Ably CLI: ```shell @@ -394,12 +408,19 @@ ably channels publish exampleChannel1 '{"name":"example","data":"Hello from CLI! ``` +If you unsubscribe from this channel in the app's UI, you will no longer receive push notifications for that channel. Send the same command again and verify that no notification is received. + +You can also send push notifications right from your app. The next step will show you how. + ## Step 5: Send push with code -Add the following functions inside `PushPage` to send push notifications programmatically using the Push Admin API: +Just as you can send push notifications through the Ably CLI or dashboard, you can also send them directly from your app using device ID (or client ID), or channel publishing methods. For channel publishing, you don't need the admin capabilities for your API key. + +Add the following functions inside `PushPage`: ```typescript +// Send push notification to a specific device ID async function sendPushToDevice() { const realtime = realtimeRef.current; if (!realtime) return; @@ -419,6 +440,7 @@ async function sendPushToDevice() { } } +// Send push notification to a specific client ID async function sendPushToClient() { const realtime = realtimeRef.current; if (!realtime) return; @@ -437,7 +459,13 @@ async function sendPushToClient() { setStatus('Error sending to client: ' + message); } } +``` + +Sending to a channel is just publishing a message on a channel with a `push` `extras` field: + + +```typescript async function sendPushToChannel() { const channel = channelRef.current; if (!channel) return; @@ -469,6 +497,16 @@ The `extras.push` object has two parts: - `notification`: Contains `title` and `body` displayed in the browser notification. - `data`: Custom key-value pairs delivered to the service worker but not shown directly. +Add the three send buttons to the JSX inside the buttons `div`: + + +```typescript + + + +``` + + ## Next steps * Understand [token authentication](/docs/auth/token) before going to production. From 9de8176f668821e888293cc7fc3fab956f7a172f Mon Sep 17 00:00:00 2001 From: Marat Alekperov Date: Fri, 20 Mar 2026 02:52:53 +0100 Subject: [PATCH 3/3] docs(push): add inline button styles to Next.js push guide Co-Authored-By: Claude Sonnet 4.6 --- src/pages/docs/push/getting-started/nextjs.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/docs/push/getting-started/nextjs.mdx b/src/pages/docs/push/getting-started/nextjs.mdx index c051ace4c3..cbe8a8ed36 100644 --- a/src/pages/docs/push/getting-started/nextjs.mdx +++ b/src/pages/docs/push/getting-started/nextjs.mdx @@ -352,7 +352,7 @@ ably push publish --client-id push-tutorial-client \ ``` -For sending pushes via a channel, we need some actual UI to be able to subscribe to this channel. So, let's build one. +To send push notifications via a channel, you first need a UI to subscribe to the channel. ## Step 4: Build the UI @@ -371,11 +371,11 @@ return ( )}
- - - - - + + + + +
@@ -501,9 +501,9 @@ Add the three send buttons to the JSX inside the buttons `div`: ```typescript - - - + + + ```