diff --git a/src/data/languages/languageData.ts b/src/data/languages/languageData.ts index 59994be332..d620039208 100644 --- a/src/data/languages/languageData.ts +++ b/src/data/languages/languageData.ts @@ -23,6 +23,7 @@ export default { nodejs: '2.21', typescript: '2.21', react: '2.21', + nextjs: '2.21', csharp: '1.2', flutter: '1.2', java: '1.6', diff --git a/src/data/languages/languageInfo.ts b/src/data/languages/languageInfo.ts index 94d92cb31c..ba1eb0c7cb 100644 --- a/src/data/languages/languageInfo.ts +++ b/src/data/languages/languageInfo.ts @@ -9,6 +9,10 @@ export default { label: 'React', syntaxHighlighterKey: 'javascript', }, + nextjs: { + label: 'Next.js', + syntaxHighlighterKey: 'javascript', + }, java: { label: 'Java', syntaxHighlighterKey: 'java', diff --git a/src/data/languages/types.ts b/src/data/languages/types.ts index c9eccfc93a..090d6fe322 100644 --- a/src/data/languages/types.ts +++ b/src/data/languages/types.ts @@ -25,6 +25,7 @@ export const languageKeys = [ 'rest', 'css', 'laravel', + 'nextjs', ] as const; export type LanguageKey = (typeof languageKeys)[number]; diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts index 6936a8f9ab..4bcd3ad8a9 100644 --- a/src/data/nav/pubsub.ts +++ b/src/data/nav/pubsub.ts @@ -29,6 +29,10 @@ export default { name: 'Node.js', link: '/docs/getting-started/node', }, + { + name: 'Next.js', + link: '/docs/getting-started/nextjs', + }, { name: 'React', link: '/docs/getting-started/react', diff --git a/src/images/content/screenshots/getting-started/pub-sub-nextjs-getting-started-guide.png b/src/images/content/screenshots/getting-started/pub-sub-nextjs-getting-started-guide.png new file mode 100644 index 0000000000..bd71bdfb42 Binary files /dev/null and b/src/images/content/screenshots/getting-started/pub-sub-nextjs-getting-started-guide.png differ diff --git a/src/pages/docs/getting-started/nextjs.mdx b/src/pages/docs/getting-started/nextjs.mdx new file mode 100644 index 0000000000..c9157892a1 --- /dev/null +++ b/src/pages/docs/getting-started/nextjs.mdx @@ -0,0 +1,551 @@ +--- +title: "Getting started: Pub/Sub in Next.js" +meta_description: "Get started with Ably Pub/Sub in Next.js. Learn how to publish and subscribe to messages, track presence, retrieve message history, and manage realtime connections in a Next.js App Router application." +meta_keywords: "Pub/Sub Next.js, Next.js PubSub, Ably Next.js SDK, realtime messaging Next.js, publish subscribe Next.js, Ably Pub/Sub guide, Next.js realtime communication, Ably tutorial Next.js, Next.js message history, presence API Next.js, Ably Pub/Sub example, realtime Pub/Sub Next.js" +languages: + - nextjs +--- + +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](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`, `subscribe`, `presence` and `history` capabilities. + +### Create a Next.js project + +Create a new Next.js project using the official scaffolding tool. Select **App Router** and **TypeScript** when prompted: + + +```shell +npx create-next-app@latest ably-pubsub-nextjs +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 +/* 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: + + +```shell +npm install ably +``` + + +### (Optional) Install Ably CLI + +Use the [Ably CLI](https://github.com/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. + +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 +``` + + + + + + +## 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](https://ably.com/accounts/any/apps/any/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. + +Because the Ably Pub/Sub client uses browser APIs such as WebSocket, it cannot run during server-side rendering. Create a new file `src/app/AblyProvider.tsx` that initializes the client inside a `useEffect` and wraps children in the `AblyProvider`: + + +```react +// src/app/AblyProvider.tsx +'use client'; + +import * as Ably from 'ably'; +import { AblyProvider as AblyReactProvider } from 'ably/react'; +import { ReactNode, useEffect, useState } from 'react'; + +export function AblyProvider({ children }: { children: ReactNode }) { + const [client, setClient] = useState(null); + + useEffect(() => { + const ably = new Ably.Realtime({ + key: '{{API_KEY}}', + clientId: 'my-first-client', + }); + setClient(ably); + return () => { + ably.close(); + }; + }, []); + + if (!client) return null; + return {children}; +} +``` + + + + +Add the `AblyProvider` to your root layout in `src/app/layout.tsx`: + + +```react +// src/app/layout.tsx +import type { ReactNode } from 'react'; +import { AblyProvider } from './AblyProvider'; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} +``` + + +This establishes a connection to Ably as soon as your application mounts in the browser. While using an API key is fine for this guide, you should use [token authentication](/docs/auth/token) in production. A [`clientId`](/docs/auth/identified-clients) 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 +// src/app/ConnectionState.tsx +'use client'; + +import { useAbly, useConnectionStateListener } from 'ably/react'; +import { useState } from 'react'; + +export function ConnectionState() { + const ably = useAbly(); + const [connectionState, setConnectionState] = useState(ably.connection.state); + + useConnectionStateListener((stateChange) => { + setConnectionState(stateChange.current); + }); + + return ( +

+ Connection: {connectionState} +

+ ); +} +``` +
+ +Update `src/app/page.tsx` to render the component: + + +```react +// src/app/page.tsx +import { ConnectionState } from './ConnectionState'; + +export default function Home() { + return ( +
+

Ably Pub/Sub - Next.js

+ +
+ ); +} +``` +
+ +Start the development server: + + +```shell +npm run dev +``` + + +Open [http://localhost:3000](http://localhost:3000) and you should see `Connection: connected`. You can also inspect the connection event in the [dev console](https://ably.com/accounts/any/apps/any/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 highlight="+2,+4,+12-14" +// src/app/page.tsx +'use client'; + +import { ChannelProvider } from 'ably/react'; +import { ConnectionState } from './ConnectionState'; + +export default function Home() { + return ( +
+

Ably Pub/Sub - Next.js

+ + + {/* Channel-scoped components go here */} + +
+ ); +} +``` +
+ +### Subscribe to a channel
+ +Use the `useChannel()` hook to subscribe to messages on a channel. Create a new file `src/app/Messages.tsx`: + + +```react +// src/app/Messages.tsx +'use client'; + +import type { Message } from 'ably'; +import { useChannel } from 'ably/react'; +import { useState } from 'react'; + +export function Messages() { + const [messages, setMessages] = useState([]); + + useChannel('my-first-channel', (message) => { + setMessages((prev) => [...prev, message]); + }); + + return ( +
+ {messages.map((msg) => ( +

+ {String(msg.data)} +

+ ))} +
+ ); +} +``` +
+ +Add `Messages` to `page.tsx` inside the `ChannelProvider`: + + +```react +// src/app/page.tsx +import { Messages } from './Messages'; + +// Inside ChannelProvider: + + + +``` + + +Test it by publishing a message from the CLI: + + +```shell +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 +// src/app/Messages.tsx +'use client'; + +import type { Message } from 'ably'; +import { useChannel } from 'ably/react'; +import { useState } from 'react'; + +export function Messages() { + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + + const { publish } = useChannel('my-first-channel', (message) => { + setMessages((prev) => [...prev, message]); + }); + + const handlePublish = () => { + if (!inputValue.trim()) return; + publish('my-first-messages', inputValue.trim()).catch(console.error); + setInputValue(''); + }; + + return ( +
+
+ {messages.map((msg) => ( +

+ {msg.clientId}: + {String(msg.data)} +

+ ))} +
+
+ setInputValue(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handlePublish()} + style={{ flex: 1, padding: '10px 12px', border: '1px solid #ccc', borderRadius: '4px', outline: 'none' }} + /> + +
+
+ ); +} +``` +
+ +Type a message and click **Publish** to see it appear in your UI. Open another browser window to see messages arriving in realtime. + +## Step 3: Join the presence set
+ +Presence enables clients to be aware of one another on the same channel. You can show who is online, provide status updates, and notify the channel when someone goes offline. + +Use the `usePresence()` and `usePresenceListener()` hooks from the Ably Pub/Sub SDK. Create a new file `src/app/PresenceStatus.tsx`: + + +```react +// src/app/PresenceStatus.tsx +'use client'; + +import { usePresence, usePresenceListener } from 'ably/react'; + +export function PresenceStatus() { + usePresence('my-first-channel', { status: "I'm here!" }); + + const { presenceData } = usePresenceListener('my-first-channel'); + + return ( +
+

+ Present ({presenceData.length}) +

+
    + {presenceData.map((member, idx) => ( +
  • + + {member.clientId} + {member.data?.status ? - {member.data.status} : null} +
  • + ))} +
+
+ ); +} +``` +
+ +Update `src/app/page.tsx` to include `PresenceStatus` and `ConnectionState` inside the `ChannelProvider`, alongside `Messages`: + + +```react +// src/app/page.tsx +'use client'; + +import { ChannelProvider } from 'ably/react'; +import { ConnectionState } from './ConnectionState'; +import { Messages } from './Messages'; +import { PresenceStatus } from './PresenceStatus'; + +export default function Home() { + return ( +
+

Ably Pub/Sub - Next.js

+ +
+
+ + +
+
+ +
+
+
+
+ ); +} +``` +
+ +Your client ID will appear in the presence list. Join presence via the CLI to see another client joining: + + +```shell +ably channels presence enter my-first-channel --data '{"status":"From CLI"}' +``` + + +## Step 4: Retrieve message history
+ +Ably stores messages for 2 minutes by default. You can [extend the storage period](/docs/storage-history/storage) if required. + +The `useChannel()` hook returns a `channel` instance. Use its `history()` method to load previously published messages on mount. Update your `Messages` component in `src/app/Messages.tsx` to load history with a `useEffect`: + + +```react +// src/app/Messages.tsx +'use client'; + +import type { Message } from 'ably'; +import { useChannel } from 'ably/react'; +import { useEffect, useState } from 'react'; + +export function Messages() { + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + + const { publish, channel } = useChannel('my-first-channel', (message) => { + setMessages((prev) => [...prev, message]); + }); + + useEffect(() => { + async function loadHistory() { + const history = await channel.history({ limit: 5 }); + setMessages((prev) => [...history.items.reverse(), ...prev]); + } + loadHistory().catch(console.error); + }, [channel]); + + const handlePublish = () => { + if (!inputValue.trim()) return; + publish('my-first-messages', inputValue.trim()).catch(console.error); + setInputValue(''); + }; + + return ( +
+
+ {messages.map((msg) => ( +

+ {msg.clientId}: + {String(msg.data)} +

+ ))} +
+
+ setInputValue(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handlePublish()} + style={{ flex: 1, padding: '10px 12px', border: '1px solid #ccc', borderRadius: '4px', outline: 'none' }} + /> + +
+
+ ); +} +``` +
+ +Publish a few messages first if needed: + + +```shell +ably channels publish --count 5 my-first-channel "Message number {{.Count}}" +``` + + +Reload the page. The last 5 messages will appear immediately, loaded from history before any new realtime messages arrive. + +Your completed application should look like this: + +![The completed Next.js Pub/Sub application.](../../../images/content/screenshots/getting-started/pub-sub-nextjs-getting-started-guide.png) + +## Next steps
+ +Continue to explore the documentation with Next.js as the selected language: + +* Understand [token authentication](/docs/auth/token) before going to production. +* Understand how to effectively [manage connections](/docs/connect#close?lang=nextjs). +* Explore more [advanced](/docs/pub-sub/advanced?lang=nextjs) Pub/Sub concepts. + +You can also explore the [Ably CLI](https://www.npmjs.com/package/@ably/cli) further, visit the Pub/Sub [API references](/docs/api/realtime-sdk?lang=javascript), or browse the [Ably Next.js Fundamentals Kit](https://github.com/ably/ably-nextjs-fundamentals-kit) for more complete examples.