## Summary Modernizes the `examples/with-ably` example off the deprecated Pages Router and `@ably-labs/react-hooks` package, onto the App Router and the React hooks that ship with ably-js v2. The previous example created the Realtime client at module scope inside `pages/_app.tsx`, which caused connections to be created during SSR. The rewrite creates it inside a `useEffect` in a client component (`app/ably-client-provider.tsx`), gated by an `AblyReadyContext` so consumer components don't try to call `ably/react` hooks before the provider is in place. ## Notable changes - Replace Pages Router with App Router. - Upgrade `ably` to v2; drop `@ably-labs/react-hooks` (hooks now ship as `ably/react`). - Bump React, react-dom and their `@types` to v19 to match recently-modernized examples like `with-supabase`. - `app/api/createTokenRequest/route.ts` now returns 400 when `clientId` is missing rather than coalescing to a shared `"NO_CLIENT_ID"` value. - Various improvements to README.md ## How I tested these changes Ran the example with Chrome and Firefox. Verified in two browser windows: - Pub/sub: messages published from one window appear in both. - Server publish: `POST /api/send-message` round-trips and broadcasts. - Presence: events propagate between windows. - No errors or warnings in browser console or dev console. Co-authored-by: Fiona Corden <fiona.corden@ably.com>
Realtime messaging with Ably
Demo: https://next-and-ably.vercel.app/
Add realtime data and interactive multi-user experiences to your Next.js apps with Ably, without the infrastructure overhead.
Use Ably in your Next.js App Router application with the ably/react hooks.
Using this demo you can:
- Send and receive realtime messages
- Get notifications of user presence on channels
- Send presence updates when a new client joins or leaves the demo
This demo uses the Ably React hooks that ship with the ably package, which manages the lifecycle of the Ably SDK instances for you, subscribing and unsubscribing to channels and events as your components mount and unmount.
Deploy your own
You will need an Ably API key to run this demo. See below for details.
How to use
Execute create-next-app with npm, Yarn, or pnpm to bootstrap the example:
npx create-next-app --example with-ably with-ably-app
yarn create next-app --example with-ably with-ably-app
pnpm create next-app --example with-ably with-ably-app
Deploy it to the cloud with Vercel (Documentation).
When deployed, ensure that you set your ABLY_API_KEY environment variable in your Vercel project settings.
Notes
Ably setup
In order to send and receive messages you will need an Ably API key. If you are not already signed up, you can sign up now for a free Ably account. Once you have an Ably account:
- Log into your app dashboard.
- Under "Your apps", click on "Manage app" for any app you wish to use for this tutorial, or create a new one with the "Create New App" button.
- Click on the "API Keys" tab.
- Copy the secret "API Key" value from your Root key.
- Create a
.env.localfile in the root of the project. - Paste the API key into your new env file:
ABLY_API_KEY=your-ably-api-key:goes-here
How it works
Client provider
app/ably-client-provider.tsx is a Client Component that creates the Ably Realtime client inside a useEffect, so no connection is attempted during SSR. It wraps its children in AblyProvider (and ChannelProvider) from ably/react, making the hooks available to descendant Client Components:
"use client";
import { useEffect, useState, type ReactNode } from "react";
import * as Ably from "ably";
import { AblyProvider, ChannelProvider } from "ably/react";
export default function AblyClientProvider({
children,
}: {
children: ReactNode;
}) {
const [clientId] = useState(
() =>
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15),
);
const [client, setClient] = useState<Ably.Realtime | null>(null);
useEffect(() => {
const ably = new Ably.Realtime({
authUrl: `/api/createTokenRequest?clientId=${clientId}`,
clientId,
});
setClient(ably);
return () => {
ably.close();
};
}, [clientId]);
if (!client) {
return <p>Connecting to Ably...</p>;
}
return (
<AblyProvider client={client}>
<ChannelProvider channelName="some-channel-name">
{children}
</ChannelProvider>
</AblyProvider>
);
}
children only mount once the client exists, so descendants can call useChannel / usePresence unconditionally without checking a readiness flag — AblyProvider is guaranteed to be above the tree.
Wrap AblyClientProvider around only the subtree that uses Ably, not your root layout. In this example the static page chrome (heading, intro paragraph, footer) lives outside the provider and renders immediately; only the chat panel waits behind the Connecting to Ably... placeholder. Mounting the provider at the root would force your whole app to wait on the WebSocket handshake before anything is visible.
The client is authenticated via a token request served by a Route Handler at app/api/createTokenRequest/route.ts, so your ABLY_API_KEY is never exposed to the browser.
useChannel (publishing and subscribing to messages)
The useChannel hook lets you subscribe to a channel and receive messages from it:
"use client";
import { useState } from "react";
import { useChannel } from "ably/react";
import type * as Ably from "ably";
export default function ChatArea() {
const [messages, setMessages] = useState<Ably.Message[]>([]);
const { channel } = useChannel("some-channel-name", (message) => {
console.log("Received Ably message", message);
setMessages((prev) => [...prev, message]);
});
// publish a message
const send = () => channel.publish("test-message", { text: "hello" });
return <button onClick={send}>Send</button>;
}
usePresence and usePresenceListener
usePresence enters presence and lets you update your presence data. usePresenceListener subscribes to presence changes on a channel:
"use client";
import { usePresence, usePresenceListener } from "ably/react";
export default function Presence() {
const { updateStatus } = usePresence("some-channel-name");
const { presenceData } = usePresenceListener("some-channel-name");
return (
<>
<button onClick={() => updateStatus("hello")}>
Update status to hello
</button>
<ul>
{presenceData.map((msg, i) => (
<li key={i}>
{msg.clientId}: {String(msg.data ?? "")}
</li>
))}
</ul>
</>
);
}
You can read more about the hooks in the Ably React docs.