From 319ecd09af585358e76609ed6bd2d5382c4f2ba3 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Thu, 16 Apr 2026 08:51:13 +0100 Subject: [PATCH 01/11] fix: replace outdated Next.js integration patterns Remove dynamic import with ssr: false from page.js (Pattern A) and move Ably client creation from component body into useEffect (Pattern C). This prevents unnecessary server-side connection attempts and stops new WebSocket connections being created on every render. References: AIT-540, ably/ably-nextjs-fundamentals-kit#11 Co-Authored-By: Claude Opus 4.6 (1M context) --- app/page.js | 6 +----- components/Chat.jsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/page.js b/app/page.js index ceb0612..3b5d0fe 100644 --- a/app/page.js +++ b/app/page.js @@ -1,9 +1,5 @@ import Head from 'next/head'; -import dynamic from 'next/dynamic'; - -const Chat = dynamic(() => import('../components/Chat'), { - ssr: false, -}); +import Chat from '../components/Chat'; export default function Home() { return ( diff --git a/components/Chat.jsx b/components/Chat.jsx index d8f64fd..bb07548 100644 --- a/components/Chat.jsx +++ b/components/Chat.jsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect, useState } from 'react'; import * as Ably from 'ably'; import { ChatClient } from '@ably/chat'; import { ChatClientProvider, ChatRoomProvider } from '@ably/chat/react'; @@ -10,8 +11,16 @@ const roomOptions = { }; export default function Chat() { - const realtimeClient = new Ably.Realtime({ authUrl: '/api' }); - const chatClient = new ChatClient(realtimeClient); + const [chatClient, setChatClient] = useState(null); + + useEffect(() => { + const realtimeClient = new Ably.Realtime({ authUrl: '/api' }); + const client = new ChatClient(realtimeClient); + setChatClient(client); + return () => { realtimeClient.close(); }; + }, []); + + if (!chatClient) return
Loading...
; return ( From e8b4c472ef64d782160a8e8541b56150529d46c1 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Thu, 16 Apr 2026 17:54:12 +0100 Subject: [PATCH 02/11] docs: update README to reflect Ably Chat SDK migration The demo was migrated from Ably Pub/Sub to the Ably Chat SDK but the README still described the old approach. This removes the misleading Pub/Sub migration banner, updates the description and tech stack to reference the Chat SDK and its React hooks, corrects the Node version requirement to 18+, updates the architecture description to reference Route Handlers instead of serverless functions, and removes the "add message history" suggestion since the app already loads the last 50 messages. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d989cc1..89eda8a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -> [!IMPORTANT] -> This repository uses the Ably Pub/Sub approach for building chat apps. We now offer Ably Chat—a new family of SDKs and APIs that streamline development and manage realtime chat complexity for you. For a modern, easier way to create chat experiences, visit our [Ably Chat documentation](https://ably.com/docs/chat). - -# Building a Realtime Chat App with Next.js, Ably, and Vercel +# Building a realtime chat app with Next.js, Ably Chat, and Vercel Live example at: @@ -13,8 +10,8 @@ This is a demo chat application with [Next.js](https://nextjs.org/) using [Ably] It demonstrates the use of: -- Pub/sub messaging -- Ably's React Hooks +- [Ably Chat SDK](https://github.com/ably/ably-chat-js) for messaging +- [Ably Chat React hooks](https://ably.com/docs/chat/setup?lang=react) (`useMessages`, `useChatClient`) - Token authentication with Ably ## Tech stack @@ -23,7 +20,7 @@ The project uses the following components: - [Next.js](https://nextjs.org/) is a React framework from [Vercel](https://vercel.com/). It is used to build static web applications with server side rendering, serverless functions and seamless hosting. It's a framework that takes the React knowledge you already have, and puts some structure and conventions in place. -- [Ably](https://ably.com/) is realtime, pub/sub messaging platform with a suite of integrated services to deliver complete realtime functionality directly to end-users. +- [Ably Chat](https://ably.com/docs/chat) is a set of SDKs built on top of Ably's realtime platform, designed for building chat experiences with minimal setup. - [Vercel](https://vercel.com/) is a hosting platform, built from the ground up to host Next.js apps, and Serverless Functions with them. @@ -34,7 +31,7 @@ The project uses the following components: ![The UI of the chat app we'll build. It is a window with speech bubbles for text.](https://cdn.glitch.com/0cb30add-c9ef-4c00-983c-e12deb0d4080%2Fchatapp.png?v=1612279601157) *The UI of the app we'll build with this walkthrough* -We'll build a realtime chat app that runs in the browser. It will be built upon the Next.js [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) template, it will contain a React component which will use Ably to send and receive messages. We'll also write a Next.js serverless function which will be used to connect to Ably. +We'll build a realtime chat app that runs in the browser. It will be built upon the Next.js [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) template, it will contain a React component which will use the Ably Chat SDK to send and receive messages. We'll also write a Next.js [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) which will be used to authenticate with Ably. ## Building & running locally @@ -44,7 +41,7 @@ In order to build and deploy this app, you will need: - **An Ably account** for sending messages: [Create an account with Ably for free](https://ably.com/signup). - **A Vercel Account** for hosting on production: [Create an account with Vercel for free](https://vercel.com/signup). -- **Node 16** or greater: [Install Node](https://nodejs.org/en/). +- **Node 18** or greater: [Install Node](https://nodejs.org/en/). You'll also need an API key from Ably to authenticate with the Ably Service. To get an API key, once you have [created an Ably account](https://ably.com/signup): @@ -86,10 +83,6 @@ In order to deploy your new chat app to Vercel you'll need to: There are a few ways that this example could be extended: -### Add message history - -There is currently no chat history in this demo, you'll only see messages that come in after you join the chat. You could expand this demo by using [Ably's rewind feature](https://ably.com/docs/storage-history/history) for up to two minutes of history for free, or with a paid account, for up to ~48 hours. - ### Add user names There aren't any usernames sent with the chat messages. This demo could be extended to introduce a username input box, and to add the current username to messages as they're sent. From 583724849264a78de39d8c38fa99b6a1f6e0a57e Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Thu, 16 Apr 2026 17:58:31 +0100 Subject: [PATCH 03/11] docs: restore message history section in README History is still not part of the demo, keep the section as-is. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 89eda8a..898500b 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ In order to deploy your new chat app to Vercel you'll need to: There are a few ways that this example could be extended: +### Add message history + +There is currently no chat history in this demo, you'll only see messages that come in after you join the chat. You could expand this demo by using [Ably's rewind feature](https://ably.com/docs/storage-history/history) for up to two minutes of history for free, or with a paid account, for up to ~48 hours. + ### Add user names There aren't any usernames sent with the chat messages. This demo could be extended to introduce a username input box, and to add the current username to messages as they're sent. From 15ea72c9e172694d5eaf1be3234bee5de066c470 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Fri, 17 Apr 2026 14:53:49 +0100 Subject: [PATCH 04/11] style: fix prettier formatting in Chat.jsx Co-Authored-By: Claude Opus 4.6 (1M context) --- components/Chat.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/Chat.jsx b/components/Chat.jsx index bb07548..10a2ccf 100644 --- a/components/Chat.jsx +++ b/components/Chat.jsx @@ -17,7 +17,9 @@ export default function Chat() { const realtimeClient = new Ably.Realtime({ authUrl: '/api' }); const client = new ChatClient(realtimeClient); setChatClient(client); - return () => { realtimeClient.close(); }; + return () => { + realtimeClient.close(); + }; }, []); if (!chatClient) return
Loading...
; From 07e81120f52d78a6f72d4c0c1ffd81b955018b64 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Fri, 17 Apr 2026 15:09:50 +0100 Subject: [PATCH 05/11] deps: update ably and @ably/chat to latest stable versions Upgrades @ably/chat from 0.6.0 (pre-release) to 1.3.1 and ably from 2.8.0 to 2.21.0 (required peer dependency). Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 109 ++++++++++++++++++++++++++++++---------------- package.json | 4 +- 2 files changed, 73 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69087d7..12b9c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "ably-nextjs-vercel-demo", "version": "0.1.0", "dependencies": { - "@ably/chat": "^0.6.0", - "ably": "2.8.0", + "@ably/chat": "^1.3.1", + "ably": "^2.21.0", "next": "^14.2.3", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -21,9 +21,9 @@ } }, "node_modules/@ably/chat": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@ably/chat/-/chat-0.6.0.tgz", - "integrity": "sha512-LS44zVRJkp05i0nRJ31JBOR+Usqp9hUtsaLZ0FroW/FrnXOMoKh0XHywGthLjmRWqH0LUmCaGvomM3pfZ/Rwxw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ably/chat/-/chat-1.3.1.tgz", + "integrity": "sha512-dbhsIz9g2cDzAWv7TvTpEZlKD+xGh+SedZw74QwhmaNjZA/ieB4401ScN/poWLAaGahkXzB2hP2DzeSl4qkDOA==", "license": "Apache-2.0", "dependencies": { "async-mutex": "^0.5.0", @@ -31,14 +31,14 @@ "lodash.clonedeep": "^4.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18", "@rollup/rollup-linux-x64-gnu": "^4.18" }, "peerDependencies": { - "ably": "^2.6.3", + "ably": "^2.19.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, @@ -52,9 +52,10 @@ } }, "node_modules/@ably/msgpack-js": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.4.0.tgz", - "integrity": "sha512-IPt/BoiQwCWubqoNik1aw/6M/DleMdrxJOUpSja6xmMRbT2p1TA8oqKWgfZabqzrq8emRNeSl/+4XABPNnW5pQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.4.1.tgz", + "integrity": "sha512-Sjxj6SOr17hExAVrsycN7u6oV4PhZcK7Z2S8dM71CH/butgO47cSo/TL6FJPCXUyDAzKkOWjMUpJGyZkEpyu4Q==", + "license": "Apache-2.0", "dependencies": { "bops": "^1.0.1" } @@ -380,6 +381,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -405,6 +407,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -416,6 +419,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", @@ -424,9 +428,10 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -438,22 +443,25 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.19.0" } }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -592,12 +600,13 @@ "dev": true }, "node_modules/ably": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/ably/-/ably-2.8.0.tgz", - "integrity": "sha512-JhOwtrbqtUR+n95jsY0syS8rIPhI1vDNfcNE1kxPhNkPaxzmYmGCo7g2xjuLZ/d4OoCuhHzX73+AVYAL70L70g==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/ably/-/ably-2.21.0.tgz", + "integrity": "sha512-Zgs3O/hqhnTEoXeV1WxoQeLaww58JbnDVFbVeEDj2kCHeuLPeavTbyf2NQBkxAM3jooN5mILaP0PDm79NOcpKw==", "license": "Apache-2.0", "dependencies": { "@ably/msgpack-js": "^0.4.0", + "dequal": "^2.0.3", "fastestsmallesttextencoderdecoder": "^1.0.22", "got": "^11.8.5", "ulid": "^2.3.0", @@ -921,6 +930,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", "integrity": "sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -929,6 +939,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bops/-/bops-1.0.1.tgz", "integrity": "sha512-qCMBuZKP36tELrrgXpAfM+gHzqa0nLsWZ+L37ncsb8txYlnAoxOPpVp+g7fK0sGkMXfA0wl8uQkESqw3v4HNag==", + "license": "MIT", "dependencies": { "base64-js": "1.0.2", "to-utf8": "0.0.1" @@ -971,6 +982,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", "engines": { "node": ">=10.6.0" } @@ -979,6 +991,7 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -1064,6 +1077,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", "dependencies": { "mimic-response": "^1.0.0" }, @@ -1187,6 +1201,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" }, @@ -1201,6 +1216,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -1218,6 +1234,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", "engines": { "node": ">=10" } @@ -1295,9 +1312,10 @@ "dev": true }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -1946,7 +1964,8 @@ "node_modules/fastestsmallesttextencoderdecoder": { "version": "1.0.22", "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==" + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0" }, "node_modules/fastq": { "version": "1.17.1", @@ -2091,6 +2110,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -2229,6 +2249,7 @@ "version": "11.8.6", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", @@ -2342,14 +2363,16 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" @@ -2936,6 +2959,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", "engines": { "node": ">=8" } @@ -2966,6 +2990,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -3073,6 +3098,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3234,6 +3260,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -3411,9 +3438,10 @@ } }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -3452,6 +3480,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3553,7 +3582,8 @@ "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" }, "node_modules/resolve-from": { "version": "4.0.0", @@ -3577,6 +3607,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -3967,7 +3998,8 @@ "node_modules/to-utf8": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", - "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==" + "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==", + "license": "MIT" }, "node_modules/ts-api-utils": { "version": "1.3.0", @@ -4134,9 +4166,10 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" }, "node_modules/uri-js": { "version": "4.4.1", @@ -4256,9 +4289,9 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 859bafb..8ea9293 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "format:check": "prettier --check --ignore-path .gitignore app components" }, "dependencies": { - "@ably/chat": "^0.6.0", - "ably": "2.8.0", + "@ably/chat": "^1.3.1", + "ably": "^2.21.0", "next": "^14.2.3", "react": "^18.2.0", "react-dom": "^18.2.0" From 32e4aa781ddb58a8cbd1e6f587b22450ac816466 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Fri, 17 Apr 2026 15:13:51 +0100 Subject: [PATCH 06/11] fix: update Chat SDK usage for v1.x breaking changes - useMessages() returns `sendMessage` instead of `send` - ChatRoomProvider takes `name` prop instead of `id` - Remove invalid `history` room option (no longer part of RoomOptions) Co-Authored-By: Claude Opus 4.6 (1M context) --- components/Chat.jsx | 6 ++---- components/ChatBox.jsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/Chat.jsx b/components/Chat.jsx index 10a2ccf..571c6fc 100644 --- a/components/Chat.jsx +++ b/components/Chat.jsx @@ -6,9 +6,7 @@ import { ChatClient } from '@ably/chat'; import { ChatClientProvider, ChatRoomProvider } from '@ably/chat/react'; import ChatBox from './ChatBox.jsx'; -const roomOptions = { - history: { limit: 50 }, -}; +const roomOptions = {}; export default function Chat() { const [chatClient, setChatClient] = useState(null); @@ -26,7 +24,7 @@ export default function Chat() { return ( - + diff --git a/components/ChatBox.jsx b/components/ChatBox.jsx index 83a5a0a..733c823 100644 --- a/components/ChatBox.jsx +++ b/components/ChatBox.jsx @@ -10,7 +10,7 @@ export default function ChatBox() { const [messages, setMessages] = useState([]); const messageTextIsEmpty = messageText.trim().length === 0; - const { send: sendMessage } = useMessages({ + const { sendMessage } = useMessages({ listener: (payload) => { const newMessage = payload.message; setMessages((prevMessages) => { From 050a702a3caf39f490d8f8c1f432e40d02c6a500 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Fri, 17 Apr 2026 15:15:28 +0100 Subject: [PATCH 07/11] fix: replace removed isSameAs/after methods with serial comparison Message.isSameAs() and Message.after() were removed in Chat SDK v1.x. Use serial string comparison for deduplication instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/ChatBox.jsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/components/ChatBox.jsx b/components/ChatBox.jsx index 733c823..4566ec6 100644 --- a/components/ChatBox.jsx +++ b/components/ChatBox.jsx @@ -14,19 +14,10 @@ export default function ChatBox() { listener: (payload) => { const newMessage = payload.message; setMessages((prevMessages) => { - if (prevMessages.some((existingMessage) => existingMessage.isSameAs(newMessage))) { + if (prevMessages.some((m) => m.serial === newMessage.serial)) { return prevMessages; } - - const index = prevMessages.findIndex((existingMessage) => existingMessage.after(newMessage)); - - const newMessages = [...prevMessages]; - if (index === -1) { - newMessages.push(newMessage); - } else { - newMessages.splice(index, 0, newMessage); - } - return newMessages; + return [...prevMessages, newMessage]; }); }, }); From 80f05a7527f37aad88585a62993fb0e8de8b6a05 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Fri, 17 Apr 2026 15:19:12 +0100 Subject: [PATCH 08/11] fix: migrate from next/head to Metadata API Move title and favicon from next/head in page.js to the metadata export in layout.js, which is the correct approach for the App Router. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/layout.js | 7 +++++-- app/page.js | 6 ------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/layout.js b/app/layout.js index 6e26cc1..eab61d0 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,8 +1,11 @@ import './globals.css'; export const metadata = { - title: 'Next.js', - description: 'Generated by Next.js', + title: 'Realtime Chat App with Ably, NextJS and Vercel', + description: 'A demo chat application with Next.js using Ably Chat', + icons: { + icon: { url: 'https://static.ably.dev/motif-red.svg?nextjs-vercel', type: 'image/svg+xml' }, + }, }; export default function RootLayout({ children }) { diff --git a/app/page.js b/app/page.js index 3b5d0fe..9f39c7d 100644 --- a/app/page.js +++ b/app/page.js @@ -1,14 +1,8 @@ -import Head from 'next/head'; import Chat from '../components/Chat'; export default function Home() { return (
- - Realtime Chat App with Ably, NextJS and Vercel - - -

Next.js Chat Demo

From b17e9eca494bff3cf8576cf1d079f2e6532426c5 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Tue, 21 Apr 2026 09:24:39 +0100 Subject: [PATCH 09/11] fix: restore sorted message insertion for out-of-order delivery Reimplement the sorted insertion logic that was removed in 050a702 using serial string comparison, so messages arriving out of order during reconnection are placed in the correct chronological position. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/ChatBox.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/ChatBox.jsx b/components/ChatBox.jsx index 4566ec6..387571f 100644 --- a/components/ChatBox.jsx +++ b/components/ChatBox.jsx @@ -14,10 +14,19 @@ export default function ChatBox() { listener: (payload) => { const newMessage = payload.message; setMessages((prevMessages) => { - if (prevMessages.some((m) => m.serial === newMessage.serial)) { + if (prevMessages.some((existingMessage) => existingMessage.serial === newMessage.serial)) { return prevMessages; } - return [...prevMessages, newMessage]; + + const index = prevMessages.findIndex((existingMessage) => existingMessage.serial > newMessage.serial); + + const newMessages = [...prevMessages]; + if (index === -1) { + newMessages.push(newMessage); + } else { + newMessages.splice(index, 0, newMessage); + } + return newMessages; }); }, }); From 559c5d60eeba6277d67f16413bd26752f9615893 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Tue, 21 Apr 2026 09:28:32 +0100 Subject: [PATCH 10/11] chore: require Node 20 Add .nvmrc, engines field in package.json, and update README prerequisites. Co-Authored-By: Claude Opus 4.6 (1M context) --- .nvmrc | 1 + README.md | 2 +- package.json | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/README.md b/README.md index 898500b..2f651e4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ In order to build and deploy this app, you will need: - **An Ably account** for sending messages: [Create an account with Ably for free](https://ably.com/signup). - **A Vercel Account** for hosting on production: [Create an account with Vercel for free](https://vercel.com/signup). -- **Node 18** or greater: [Install Node](https://nodejs.org/en/). +- **Node 20** or greater: [Install Node](https://nodejs.org/en/). You'll also need an API key from Ably to authenticate with the Ably Service. To get an API key, once you have [created an Ably account](https://ably.com/signup): diff --git a/package.json b/package.json index 8ea9293..dd2ed85 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "ably-nextjs-vercel-demo", "version": "0.1.0", "private": true, + "engines": { + "node": ">=20" + }, "scripts": { "dev": "next dev", "build": "next build", From f986d9c675b3130eb1388542be97d7edfd0e3c92 Mon Sep 17 00:00:00 2001 From: Fiona Corden Date: Tue, 21 Apr 2026 09:50:38 +0100 Subject: [PATCH 11/11] fix: use SDK-recommended sort pattern for message ordering Sort messages by serial using comparison operators as shown in the Chat SDK documentation, rather than findIndex/splice insertion. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/ChatBox.jsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/components/ChatBox.jsx b/components/ChatBox.jsx index 387571f..58807af 100644 --- a/components/ChatBox.jsx +++ b/components/ChatBox.jsx @@ -17,16 +17,7 @@ export default function ChatBox() { if (prevMessages.some((existingMessage) => existingMessage.serial === newMessage.serial)) { return prevMessages; } - - const index = prevMessages.findIndex((existingMessage) => existingMessage.serial > newMessage.serial); - - const newMessages = [...prevMessages]; - if (index === -1) { - newMessages.push(newMessage); - } else { - newMessages.splice(index, 0, newMessage); - } - return newMessages; + return [...prevMessages, newMessage].sort((a, b) => (a.serial < b.serial ? -1 : b.serial < a.serial ? 1 : 0)); }); }, });