Getting started: Pub/Sub in Next.js

This guide will get you started with Ably Pub/Sub in a new Next.js application.

You'll establish a realtime connection to Ably and learn to publish and subscribe to messages. You'll also implement presence to track other online clients, and learn how to retrieve message history.

Prerequisites

  1. Sign up for an Ably account.
  2. Create a new app, and create your first API key in the API Keys tab of the dashboard.
  3. Your API key will need the publish, subscribe, presence and history capabilities.

Create a Next.js project

Create a new Next.js project using the official scaffolding tool. The flags select TypeScript, the App Router and a src/ directory, which is where the files in this guide live:

npx create-next-app@latest ably-pubsub-nextjs --yes --typescript --app --src-dir --no-tailwind
cd ably-pubsub-nextjs

Update globals.css

Replace the contents of src/app/globals.css with the following to reset browser defaults and ensure consistent font sizing across all elements including inputs and buttons:

css

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

/* src/app/globals.css */
html {
  height: 100%;
}

html,
body {
  max-width: 100vw;
  overflow-x: hidden;
}

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  color: #171717;
  background: #ffffff;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 15px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

*,
input,
button {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
  font-size: inherit;
  font-family: inherit;
}

Install Ably Pub/Sub JavaScript SDK

Install the Ably Pub/Sub JavaScript SDK:

npm install @ably/pubsub-device

(Optional) Install Ably CLI

Use the Ably CLI as an additional client to quickly test Pub/Sub features. It can simulate other clients by publishing messages, subscribing to channels, and managing presence states.

ably init installs the Ably CLI, authenticates, and sets the default app and API key in a single command:

npx @ably/cli init

Step 1: Connect to Ably

Clients establish a connection with Ably when they instantiate an SDK instance. This enables them to send and receive messages in realtime across channels.

Open up the dev console of your first app before you start so that you can see what happens.

Set up AblyProvider

The Ably Pub/Sub SDK provides React hooks and context providers that make it easy to use Pub/Sub features in your components.

The Ably Pub/Sub client and its React hooks are designed to run in the browser, not during server-side rendering. Create a new file src/app/AblyProvider.tsx that creates the client once, at module level, and wraps children in the PubSubClientProvider:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// src/app/AblyProvider.tsx
'use client';

import { createClient } from '@ably/pubsub-device';
import { PubSubClientProvider } from '@ably/pubsub-device/react';
import { ReactNode, useEffect } from 'react';

const client = createClient({
  key: 'demokey:*****',
  clientId: 'my-first-client',
  autoConnect: false,
});

export default function AblyProvider({ children }: { children: ReactNode }) {
  useEffect(() => {
    client.connect();
  }, []);

  return <PubSubClientProvider client={client}>{children}</PubSubClientProvider>;
}
API key:
DEMO ONLY

While using an API key is fine for this guide, you should use token authentication in production. A clientId identifies the client, which is required for features such as presence.

Display the connection state

To display the connection state in your UI, create a client component at src/app/ConnectionState.tsx:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// src/app/ConnectionState.tsx
'use client';

import { usePubSubClient, useConnectionStateListener } from '@ably/pubsub-device/react';
import { useState } from 'react';

export function ConnectionState() {
  const client = usePubSubClient();
  const [connectionState, setConnectionState] = useState(client.connection.state);

  useConnectionStateListener((stateChange) => {
    setConnectionState(stateChange.current);
  });

  return (
    <p style={{ color: '#555', padding: '8px 10px', background: '#f0f0f0', borderRadius: '5px' }}>
      Connection: <strong>{connectionState}</strong>
    </p>
  );
}

Replace the contents of src/app/page.tsx to render the component inside the AblyProvider. The page loads AblyProvider with next/dynamic and ssr: false, so the module that creates the client is only ever loaded in the browser:

React
1// src/app/page.tsx2'use client';3 4import dynamic from 'next/dynamic';5import { ConnectionState } from './ConnectionState';6 7const AblyProvider = dynamic(() => import('./AblyProvider'), { ssr: false });8 9export default function Home() {10  return (11    <main style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '900px', margin: '0 auto' }}>12      <h1 style={{ marginBottom: '20px', fontSize: '36px', textAlign: 'center' }}>Ably Pub/Sub - Next.js</h1>13      <AblyProvider>14        <ConnectionState />15      </AblyProvider>16    </main>17  );18}

Start the development server:

npm run dev

Open http://localhost:3000 and you should see Connection: connected. You can also inspect the connection event in the dev console of your app.

Step 2: Subscribe to a channel and publish a message

To publish and subscribe to messages on a channel use the ChannelProvider component from the Ably Pub/Sub SDK, which scopes child components to a specific channel.

ChannelProvider

The ChannelProvider must be nested inside the AblyProvider. Update src/app/page.tsx to include the ChannelProvider:

React
1// src/app/page.tsx2'use client';3 4import { ChannelProvider } from '@ably/pubsub-device/react';5import dynamic from 'next/dynamic';6import { ConnectionState } from './ConnectionState';7 8const AblyProvider = dynamic(() => import('./AblyProvider'), { ssr: false });9 10export default function Home() {11  return (12    <main style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '900px', margin: '0 auto' }}>13      <h1 style={{ marginBottom: '20px', fontSize: '36px', textAlign: 'center' }}>Ably Pub/Sub - Next.js</h1>14      <AblyProvider>15        <ConnectionState />16        <ChannelProvider channelName='my-first-channel'>17          {/* Channel-scoped components go here */}18        </ChannelProvider>19      </AblyProvider>20    </main>21  );22}

Subscribe to a channel

Use the useChannel() hook to subscribe to messages on a channel. Create a new file src/app/Messages.tsx:

React

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// src/app/Messages.tsx
'use client';

import type { Message } from '@ably/pubsub-device';
import { useChannel } from '@ably/pubsub-device/react';
import { useState } from 'react';

export function Messages() {
  const [messages, setMessages] = useState<Message[]>([]);

  useChannel('my-first-channel', (message) => {
    setMessages((prev) => [...prev, message]);
  });

  return (
    <div style={{ border: '1px solid #ddd', borderRadius: '4px', padding: '12px', width: '400px', height: '250px', overflowY: 'auto', background: '#fff' }}>
      {messages.map((msg) => (
        <p key={msg.id} style={{ margin: '4px 0', borderLeft: '3px solid #007bff', paddingLeft: '8px', color: '#171717' }}>
          {String(msg.data)}
        </p>
      ))}
    </div>
  );
}

Add Messages to page.tsx inside the ChannelProvider:

React
1// src/app/page.tsx2'use client';3 4import { ChannelProvider } from '@ably/pubsub-device/react';5import dynamic from 'next/dynamic';6import { ConnectionState } from './ConnectionState';7import { Messages } from './Messages';8 9const AblyProvider = dynamic(() => import('./AblyProvider'), { ssr: false });10 11export default function Home() {12  return (13    <main style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '900px', margin: '0 auto' }}>14      <h1 style={{ marginBottom: '20px', fontSize: '36px', textAlign: 'center' }}>Ably Pub/Sub - Next.js</h1>15      <AblyProvider>16        <ConnectionState />17        <ChannelProvider channelName='my-first-channel'>18          <Messages />19        </ChannelProvider>20      </AblyProvider>21    </main>22  );23}

Test it by publishing a message from the CLI:

ably channels publish my-first-channel 'Hello from CLI!'

Publish a message

The useChannel() hook also returns a publish method. Update src/app/Messages.tsx to add a message input:

React
1// src/app/Messages.tsx2'use client';3 4import type { Message } from '@ably/pubsub-device';5import { useChannel } from '@ably/pubsub-device/react';6import { useState } from 'react';7 8export function Messages() {9  const [messages, setMessages] = useState<Message[]>([]);10  const [inputValue, setInputValue] = useState('');11 12  const { publish } = useChannel('my-first-channel', (message) => {13    setMessages(