Skip to content

Commit 948c49f

Browse files
maratalclaude
andcommitted
refactor(nextjs-push-guide): restructure to 4-step layout matching flutter/react-native guides
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a9dde4f commit 948c49f

1 file changed

Lines changed: 75 additions & 133 deletions

File tree

src/pages/docs/push/getting-started/nextjs.mdx

Lines changed: 75 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,27 @@ You'll learn how to set up an Ably Realtime client with push notification suppor
1212

1313
1. [Sign up](https://ably.com/signup) for an Ably account.
1414
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.
15-
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.
16-
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.
15+
* Your API key needs the `publish`, `subscribe` capabilities.
16+
* Also add the `push-admin` capability if you're using the same API key to send a push notification. In production this would more likely be a server using a different API key.
17+
3. Add a rule to a channel so you can test sending push notification via a channel. Select [**Rules**](https://ably.com/accounts/any/apps/any/app_namespaces) in the Ably dashboard, add a new rule and enable the **Push notifications** option.
18+
4. Install [Node.js](https://nodejs.org/) 18 or higher.
1719
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).
18-
6. [Node.js](https://nodejs.org/) 18 or higher.
1920

2021
### (Optional) Install Ably CLI <a id="install-cli"/>
2122

2223
Use the [Ably CLI](/docs/platform/tools/cli) as an additional client to quickly test Pub/Sub features and push notifications.
2324

2425
1. Install the Ably CLI:
2526

26-
<Code>
27+
<Code fixed="true">
2728
```shell
2829
npm install -g @ably/cli
2930
```
3031
</Code>
3132

3233
2. Run the following to log in to your Ably account and set the default app and API key:
3334

34-
<Code>
35+
<Code fixed="true">
3536
```shell
3637
ably login
3738
```
@@ -41,7 +42,7 @@ ably login
4142

4243
Create a new Next.js project using the official create command:
4344

44-
<Code>
45+
<Code fixed="true">
4546
```shell
4647
npx create-next-app@latest ably-push-tutorial --typescript --app --no-tailwind --eslint --src-dir
4748
cd ably-push-tutorial
@@ -50,7 +51,7 @@ cd ably-push-tutorial
5051

5152
Then install the Ably SDK and React hooks package:
5253

53-
<Code>
54+
<Code fixed="true">
5455
```shell
5556
npm install ably
5657
```
@@ -65,7 +66,7 @@ Because the Ably SDK runs in the browser, the component must not be server-side
6566
Create `src/app/push/page.tsx` as the entry point:
6667

6768
<Code>
68-
```javascript
69+
```react
6970
'use client';
7071
7172
import dynamic from 'next/dynamic';
@@ -78,10 +79,10 @@ export default function PushPage() {
7879
```
7980
</Code>
8081

81-
Then create `src/app/push/PushApp.tsx`. Because this module is only ever loaded client-side (due to `ssr: false`), it is safe to instantiate the Ably client at module scope. In a production app you would instead use `useAbly` from within a component to avoid creating a global client instance:
82+
Then create `src/app/push/PushApp.tsx`. Because this module is only ever loaded client-side (due to `ssr: false`), it is safe to instantiate the Ably client at module scope:
8283

8384
<Code>
84-
```javascript
85+
```react
8586
'use client';
8687
8788
import * as Ably from 'ably';
@@ -92,7 +93,7 @@ import { PushActivationBanner } from './PushActivationBanner';
9293
import { ChannelSubscription } from './ChannelSubscription';
9394
import { NotificationLog } from './NotificationLog';
9495
95-
const CHANNEL_NAME = 'exampleChannel1';
96+
const CHANNEL_NAME = 'my-first-push-channel';
9697
9798
const client = new Ably.Realtime({
9899
key: '{{API_KEY}}', // Do not use an API key in production — use token authentication instead
@@ -140,14 +141,35 @@ Key configuration options:
140141
- **`plugins`**: The `AblyPushPlugin` enables push notification support.
141142
- **`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`.
142143

143-
`PushActivationBanner` sits directly under `AblyProvider` and handles device-level push activation. `ChannelSubscription` sits inside a `ChannelProvider`, which scopes it to `exampleChannel1`.
144+
`PushActivationBanner` sits directly under `AblyProvider` and handles device-level push activation. `ChannelSubscription` sits inside a `ChannelProvider`, which scopes it to `my-first-push-channel`.
145+
146+
Create `src/app/push/NotificationLog.tsx` as a presentational component with no Ably dependency:
147+
148+
<Code>
149+
```react
150+
export function NotificationLog({ output, onClear }: { output: string[]; onClear: () => void }) {
151+
const buttonStyle = { padding: '12px 20px', margin: '5px 0', width: '100%', display: 'block', background: '#6c757d', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px' };
152+
153+
return (
154+
<>
155+
<div style={{ border: '1px solid #ddd', borderRadius: '4px', padding: '12px', minHeight: '500px', background: '#fff' }}>
156+
{output.map((entry, i) => (
157+
<p key={i} style={{ margin: '4px 0', borderLeft: '3px solid #007bff', paddingLeft: '8px', color: '#171717', whiteSpace: 'pre-line' }}>{entry}</p>
158+
))}
159+
</div>
160+
<button onClick={onClear} style={buttonStyle}>Clear</button>
161+
</>
162+
);
163+
}
164+
```
165+
</Code>
144166

145-
## Step 2: Activate push notifications <a id="step-2"/>
167+
## Step 2: Set up push notifications <a id="step-2"/>
146168

147169
Create `src/app/push/PushActivationBanner.tsx`. This component uses the `usePushActivation` hook to activate and deactivate the device.
148170

149171
<Code>
150-
```javascript
172+
```react
151173
'use client';
152174
153175
import { useEffect } from 'react';
@@ -194,18 +216,14 @@ export function PushActivationBanner({ onLog, onDeviceChange }: { onLog: (msg: s
194216

195217
- **`activate`**: Registers the browser for push notifications. Requests notification permission, registers the service worker, and records the device with Ably.
196218
- **`deactivate`**: Removes the device registration from Ably's servers. Call this only on explicit user opt-out.
197-
- **`localDevice`**: The current `LocalDevice` if activated, `null` otherwise. Reactive — updates immediately when `activate` or `deactivate` is called, and is re-populated from `localStorage` on page load if the device was activated in a prior session. Here it is propagated upward via `onDeviceChange` so the parent can display the device ID above the log.
198-
199-
## Step 3: Receive push notifications <a id="step-3"/>
219+
- **`localDevice`**: The current `LocalDevice` if activated, `null` otherwise. Reactive — updates immediately when `activate` or `deactivate` is called, and is re-populated from `localStorage` on page load if the device was activated in a prior session.
200220

201221
A service worker runs in the background and receives push notifications even when the page is not open. In Next.js, place the service worker in `public/` so it is served from the root path.
202222

203-
### Create the service worker <a id="step-3-service-worker"/>
204-
205223
Create `public/service-worker.js`:
206224

207225
<Code>
208-
```javascript
226+
```react
209227
// Handle push events
210228
self.addEventListener('push', (event) => {
211229
const eventData = event.data.json();
@@ -232,12 +250,10 @@ self.addEventListener('push', (event) => {
232250
```
233251
</Code>
234252

235-
### Handle notification clicks <a id="step-3-notification-clicks"/>
236-
237253
Add a `notificationclick` listener in `public/service-worker.js` to handle what happens when the user clicks a notification:
238254

239255
<Code>
240-
```javascript
256+
```react
241257
// Handle notification clicks
242258
self.addEventListener('notificationclick', (event) => {
243259
event.notification.close();
@@ -274,12 +290,12 @@ self.addEventListener('notificationclick', (event) => {
274290

275291
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.
276292

277-
### Handle notifications in the component <a id="step-3-handle-notifications"/>
293+
### Handle push notifications <a id="step-2-handle"/>
278294

279295
Add a `useEffect` to `PushApp.tsx` to receive messages forwarded from the service worker and write them to the log:
280296

281297
<Code>
282-
```javascript
298+
```react
283299
import { useEffect, useState } from 'react';
284300
285301
// Inside PushApp, after the log function:
@@ -307,105 +323,20 @@ useEffect(() => {
307323

308324
This listener is placed in `PushApp` rather than in a child component because push events are not channel-specific — a notification can arrive for any channel, or directly by device or client ID.
309325

310-
### Build the notification log <a id="step-3-notification-log"/>
311-
312-
Create `src/app/push/NotificationLog.tsx` as a presentational component with no Ably dependency:
313-
314-
<Code>
315-
```javascript
316-
export function NotificationLog({ output, onClear }: { output: string[]; onClear: () => void }) {
317-
const buttonStyle = { padding: '12px 20px', margin: '5px 0', width: '100%', display: 'block', background: '#6c757d', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px' };
318-
319-
return (
320-
<>
321-
<div style={{ border: '1px solid #ddd', borderRadius: '4px', padding: '12px', minHeight: '500px', background: '#fff' }}>
322-
{output.map((entry, i) => (
323-
<p key={i} style={{ margin: '4px 0', borderLeft: '3px solid #007bff', paddingLeft: '8px', color: '#171717', whiteSpace: 'pre-line' }}>{entry}</p>
324-
))}
325-
</div>
326-
<button onClick={onClear} style={buttonStyle}>Clear</button>
327-
</>
328-
);
329-
}
330-
```
331-
</Code>
332-
333-
Start the development server:
334-
335-
<Code>
336-
```shell
337-
npm run dev
338-
```
339-
</Code>
340-
341-
Navigate to `http://localhost:3000/push` in your browser. Click **Activate Push**, grant notification permission, and wait for the device ID to appear.
342-
343-
### Test push notifications <a id="step-3-test"/>
344-
345-
Once the device is activated, publish a test push notification directly to your client ID using the [Ably CLI](/docs/platform/tools/cli):
346-
347-
<Code>
348-
```shell
349-
ably push publish --client-id push-tutorial-client \
350-
--title "Test push" \
351-
--body "Hello from CLI!" \
352-
--data '{"foo":"bar"}'
353-
```
354-
</Code>
355-
356-
Or from code:
357-
358-
<Code>
359-
```javascript
360-
await client.push.admin.publish(
361-
{ clientId: 'push-tutorial-client' },
362-
{
363-
notification: { title: 'Test push', body: 'Hello from code!' },
364-
data: { foo: 'bar' },
365-
},
366-
);
367-
```
368-
</Code>
369-
370-
A native browser notification should appear and the log should display the received push event. You can also send to the specific device ID shown in the log:
371-
372-
<Code>
373-
```shell
374-
ably push publish --device-id <your-device-id> \
375-
--title "Test push" \
376-
--body "Hello from CLI!"
377-
```
378-
</Code>
379-
380-
<Code>
381-
```javascript
382-
await client.push.admin.publish(
383-
{ deviceId: client.device().id },
384-
{
385-
notification: { title: 'Test push', body: 'Hello from code!' },
386-
},
387-
);
388-
```
389-
</Code>
390-
391-
<Aside data-type='note'>
392-
Sending push notifications using `deviceId` or `clientId` requires the `push-admin` capability on your API key. In production, send push notifications from your backend server rather than from the client.
393-
</Aside>
394-
395-
## Step 4: Subscribe to channel push <a id="step-4"/>
326+
## Step 3: Subscribe to channel push notifications <a id="step-3"/>
396327

397328
Create `src/app/push/ChannelSubscription.tsx`. This component uses two hooks:
398329

399330
- `usePush` to manage push subscriptions for the channel and expose `isActivated`
400331
- `useChannel` to subscribe to realtime messages on the same channel
401332

402333
<Code>
403-
```javascript
334+
```react
404335
'use client';
405336
406337
import { useChannel, usePush } from 'ably/react';
407338
408-
const CHANNEL_NAME = 'exampleChannel1';
339+
const CHANNEL_NAME = 'my-first-push-channel';
409340
410341
export function ChannelSubscription({ onLog }: { onLog: (msg: string) => void }) {
411342
const {
@@ -464,38 +395,49 @@ export function ChannelSubscription({ onLog }: { onLog: (msg: string) => void })
464395

465396
`usePush` returns `isActivated` — a reactive boolean shared with `usePushActivation` via a module-level store. When `PushActivationBanner` calls `activate()`, all `usePush` instances update automatically, so the subscribe buttons enable without any extra wiring.
466397

467-
To test channel push, subscribe to the channel in the UI then publish a push notification to the channel using the [Ably CLI](/docs/platform/tools/cli):
398+
Run your app on a real device or compatible browser:
468399

469-
<Code>
400+
<Code fixed="true">
470401
```shell
471-
ably push publish --channel exampleChannel1 \
402+
npm run dev
403+
```
404+
</Code>
405+
406+
Navigate to `http://localhost:3000/push` in your browser.
407+
408+
## Step 4: Publish a push notification <a id="step-4"/>
409+
410+
In the app click **Activate Push** and wait until the status message displays your device ID.
411+
412+
### Publish directly to your device <a id="step-4-direct"/>
413+
414+
Publish a push notification directly to your client ID (or device ID using `--device-id` instead of `--client-id`) via the [Ably CLI](/docs/platform/tools/cli):
415+
416+
<Code fixed="true">
417+
```shell
418+
ably push publish --client-id push-tutorial-client \
472419
--title "Hello" \
473420
--body "World!" \
474-
--message '{"name":"greeting","data":"Hello World!"}'
421+
--data '{"foo":"bar"}'
475422
```
476423
</Code>
477424

478-
Or from code:
425+
### Publish via a channel <a id="step-4-channel"/>
479426

480-
<Code>
481-
```javascript
482-
await channel.publish({
483-
name: 'example',
484-
data: 'Hello from code!',
485-
extras: {
486-
push: {
487-
notification: { title: 'Channel Push', body: 'Hello from code!' },
488-
data: { foo: 'bar' },
489-
},
490-
},
491-
});
427+
Click **Subscribe to Channel** in the app, then publish a push notification to the channel using the [Ably CLI](/docs/platform/tools/cli):
428+
429+
<Code fixed="true">
430+
```shell
431+
ably push publish --channel my-first-push-channel \
432+
--title "Hello" \
433+
--body "World!" \
434+
--message '{"name":"greeting","data":"Hello World!"}'
492435
```
493436
</Code>
494437

495-
When publishing from code, the `push` object must be included in the `extras` of the realtime message. The `extras.push` object has two parts:
438+
If you click **Unsubscribe from Channel**, the device no longer receives push notifications for that channel. Send the same command again and verify that no notification is received.
496439

497-
- `notification`: Contains `title` and `body` displayed in the browser notification.
498-
- `data`: Custom key-value pairs delivered to the service worker but not shown directly.
440+
To see the full list of options for sending push notifications with the Ably CLI, run `ably push publish --help` or see the [Ably CLI push documentation](/docs/cli/push). To send push notifications from your own server code instead of the CLI, see [Push notification publishing](https://ably.com/docs/push/publish).
499441

500442
![Screenshot of the Next.js push tutorial application showing push notifications received](../../../../images/content/screenshots/getting-started/nextjs-push-getting-started-guide.png)
501443

0 commit comments

Comments
 (0)