
在 Next.js App Router 中使用 Ably 实现实时消息与在线状态with-ably 示例完全解读【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js在 Next.js 应用中叠加实时能力消息推送、多用户协作、在线状态并不需要自建 WebSocket 基础设施——通过 Ably 的托管实时数据通道配合ably包随附的 React Hooksably/react即可在 App Router 架构下快速落地发布/订阅消息与 Presence 在线状态功能。本指南以官方仓库examples/with-ably为蓝本从零开始讲解如何搭建环境、用AblyProvider/ChannelProvider管理客户端生命周期、用useChannel/usePresence/usePresenceListener收发消息与跟踪用户上下线并剖析 Token 鉴权与“服务器代为发布消息”的完整实现。读完你将能够独立复刻一套聊天面板 在线成员列表 服务端代发消息的实时演示应用并理解每个关键环节在真实 Next.js 工程中是如何被组织的。示例概览这个示例能做什么examples/with-ably是一个可直接运行的 Next.js App Router 演示工程其目标是展示无需管理任何实时基础设施用少量客户端代码为 Next.js 应用接入真实时数据。示例页面提供三类交互能力实时消息收发pub/sub通过浏览器 WebSocket 连接订阅频道边发边收实时消息用户在线状态Presence通知查看当前有哪些客户端进入/离开频道、各自的状态数据Presence 状态更新当新客户端加入或离开演示页时其他所有客户端都能实时感知并刷新列表。这套能力由ablynpm 包中随附的 React Hooks即ably/react承担。Hooks 帮你管理 Ably SDK 实例的完整生命周期组件挂载时自动订阅频道与事件组件卸载时自动取消订阅无需手写清理逻辑。工程的关键源码结构如下均在 examples/with-ably 目录下文件职责app/layout.tsx根布局注入全局样式与页面元数据app/page.tsx服务端页面组件仅渲染客户端根组件app/home-client.tsx客户端 UI聊天区、Presence 列表与各类按钮app/ably-client-provider.tsx客户端 Provider创建 Ably Realtime 连接并向上层注入上下文app/api/createTokenRequest/route.tsRoute Handler为客户端签发 Token Request鉴权app/api/send-message/route.tsRoute Handler以服务端身份向频道发布消息types.d.ts演示用消息类型定义styles/globals.css全局样式容器布局、按钮配色等依赖方面package.json 中最核心的依赖是ably示例锁定在^2.21.0配合next、react、react-dom与 TypeScript 工具链运行ably/react子路径即来自该依赖。快速启动clone 一个可以直接跑的实时应用方式一Deploy your own一键部署该示例专为 Vercel 一键部署设计了环境变量接入点击示例 README 中提供的 Vercel Deploy 按钮即可直接 clone 本仓库的examples/with-ably目录作为新项目。部署向导会要求你配置项目名with-ably环境变量ABLY_API_KEY来源为 Ably 控制台务必记住部署到 Vercel 后还需在 Vercel 项目的 Settings → Environment Variables 中再次设置ABLY_API_KEY否则服务端 Route Handler 将无法完成 Token 签发与代发消息。方式二create-next-app 本地引导使用create-next-app位于仓库 packages/create-next-app并指定--example with-ably即可把示例完整拉取到本地以下 npm / Yarn / pnpm 三种命令等价npx create-next-app --example with-ably with-ably-appyarn create next-app --example with-ably with-ably-apppnpm create next-app --example with-ably with-ably-app启动后本地运行npm run dev或pnpm dev打开开发服务器地址即可看到Connecting to Ably...占位符与聊天/在线列表界面。前提Ably 账号与 API Key无论本地还是云端运行收发消息都依赖一个 Ably API Key。若你还没有 Ably 账号可免费注册一个。获取 Key 的完整步骤登录 Ably 应用控制台Dashboard在Your apps区域为本次教程选择一个已有应用并点击Manage app或点击 Create New App 新建应用进入API Keys标签页从 Root key根密钥中复制API Key的秘密值在项目根目录创建.env.local文件将 API Key 粘贴到该文件中形如ABLY_API_KEYyour-ably-api-key:goes-here.env.local会被 Next.js 自动加载为process.env且默认不会提交到版本库。示例中的两个 Route Handler 均在服务端读取该变量详见下文鉴权与安全小节。核心架构把实时能力注入到组件树中理解这个示例的关键是把握它与 Next.js服务端组件/客户端组件边界的配合方式。整个实时链路被拆成三层创建连接只做一次在 Client Provider 内部通过useEffect创建Ably.Realtime实例注入上下文Provider用AblyProvider必要时再叠加ChannelProvider包裹子树让所有后代 Client Components 都能拿到 client/channel消费能力Hooks后代组件通过useChannel、usePresence、usePresenceListener订阅消息、上报/读取在线状态。客户端 Providerably-client-provider.tsxably-client-provider.tsx完整代码见 app/ably-client-provider.tsx是一个 Client Component负责创建 Ably Realtime 客户端、维护其生命周期并向上层组件提供上下文use client; import { useEffect, useState, type ReactNode } from react; import * as Ably from ably; import { AblyProvider, ChannelProvider } from ably/react; function randomClientId() { return ( Math.random().toString(36).substring(2, 15) Math.random().toString(36).substring(2, 15) ); } export default function AblyClientProvider({ children, }: { children: ReactNode; }) { const [clientId] useState(() randomClientId()); const [client, setClient] useStateAbly.Realtime | null(null); useEffect(() { const ably new Ably.Realtime({ authUrl: /api/createTokenRequest?clientId${clientId}, clientId, }); setClient(ably); return () { ably.close(); }; }, [clientId]); if (!client) { return pConnecting to Ably.../p; } return ( AblyProvider client{client} ChannelProvider channelNamesome-channel-name {children} /ChannelProvider /AblyProvider ); }逐段拆解其中的关键设计在useEffect中创建连接避免 SSR 阶段发起连接。Ably Realtime 底层是 WebSocket。若在服务端渲染阶段就实例化客户端Node 侧并没有可供建立的长连接环境也会拖慢首屏 HTML 输出。因此这里把new Ably.Realtime(...)放进useEffect只在浏览器挂载后才执行client初始值为null未就绪时直接渲染pConnecting to Ably.../p占位符。随组件卸载关闭连接。useEffect的清理函数调用ably.close()。这意味着当 Provider 被卸载例如用户离开应用或路由销毁该子树时WebSocket 连接会被主动关闭不会泄漏连接与内存。随机 clientId 保证每个标签页都是独立用户。通过两次拼接Math.random().toString(36).substring(2, 15)生成一个足够随机的短字符串作为clientId。它既作为连接的身份标识传入 SDK也用于向鉴权接口请求 Token。由于每次进入页面都重新随机同一浏览器的不同标签页会被视作不同用户——这正是 Presence 演示多人在线效果的基础。Provider 保证先就绪后挂载子树。children只有在client创建成功后才被渲染因此后代组件可以无条件调用useChannel/usePresence不需要先检查某个是否就绪的 flag——因为AblyProvider一定已经在组件树上方就位。把 Provider 放在尽量靠下的位置只包裹真正需要实时的子树。这是一个重要的实践要点建议只把AblyClientProvider包在用到 Ably 的那段子树外围而不要包在整个根布局root layout上。示例中页面头部标题、简介段落、页脚这些静态页面骨架都在 Provider 之外可以随首屏 HTML 立即渲染只有中间的聊天面板会先显示Connecting to Ably...占位符等连接建立后再替换为真实内容。反过来如果把 Provider 挂到根布局那么整个应用都要等 WebSocket 握手完成才有任何内容可见会显著拖慢感知性能。该示例的实际接入方式见 app/home-client.tsxAblyClientProvider ChatArea / /AblyClientProvider鉴权与安全用 Route Handler 签发 Token Request上面 Provider 里给 SDK 传了authUrl: /api/createTokenRequest?clientId...也就是让浏览器向自己的 Next.js 服务端请求鉴权凭证而不是把ABLY_API_KEY直接写进客户端代码。对应的 Route Handler 完整实现位于 app/api/createTokenRequest/route.tsimport * as Ably from ably; import { type NextRequest, NextResponse } from next/server; export async function GET(request: NextRequest) { if (!process.env.ABLY_API_KEY) { return NextResponse.json( { error: Missing ABLY_API_KEY environment variable }, { status: 500 }, ); } const clientId request.nextUrl.searchParams.get(clientId); if (!clientId) { return NextResponse.json( { error: Missing clientId query parameter }, { status: 400 }, ); } const client new Ably.Rest(process.env.ABLY_API_KEY); const tokenRequestData await client.auth.createTokenRequest({ clientId }); return NextResponse.json(tokenRequestData); }该接口的职责与安全收益可以从以下几点理解密钥只存在于服务端。process.env.ABLY_API_KEY在 Route Handler运行于 Node.js 服务端中读取浏览器永远拿不到你的 Root API Key避免密钥随打包产物泄露。先校验再签发。接口先检查ABLY_API_KEY是否缺失缺失返回 500再校验 URL 查询参数里的clientId缺失返回 400双重防御避免带着脏参数去请求 Ably。createTokenRequest生成的是受限凭证。服务端用Ably.Rest实例调用auth.createTokenRequest({ clientId })产出一份绑定该clientId的 Token Request JSON 并原样返回浏览器端 SDK 拿到后会自动用它与 Ably 完成 Token 交换并建立 WebSocket。浏览器侧的使用方式即 Provider 中的配置——authUrl指向该接口并携带自己的clientId同时 SDK 构造参数里也显式声明clientId二者保持一致保证 Ably 端把该连接归到对应身份名下Presence 中呈现的clientId才与消息发送者一一对应。实时消息useChannel 的订阅与发布useChannel用于订阅一个频道并接收其中的消息。README 给出了极简的抽象示例use client; import { useState } from react; import { useChannel } from ably/react; import type * as Ably from ably; export default function ChatArea() { const [messages, setMessages] useStateAbly.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; }这段代码演示了useChannel的两个核心能力订阅useChannel(channelName, callback)的第一个参数是要订阅的频道名第二个回调会在每次收到该频道消息时触发。由于演示应用通过ChannelProvider把默认频道设定为some-channel-name这里实际订阅的就是这个频道在未使用ChannelProvider的场景下也可以直接传入频道名。发布hook 返回值解构出的channel对象提供.publish(eventName, data)方法例如channel.publish(test-message, { text: hello })即以事件名test-message把一条 JSON 消息发到频道所有订阅该频道的其他客户端会立即通过订阅回调收到。示例工程中的真实实现见 app/home-client.tsx 的ChatArea它把收到的message.data断言为TextMessage{ text: string }定义见 types.d.ts并追加到messages状态数组从而驱动消息列表渲染Send A Message 按钮则携带发送者身份构造一条TextMessage后调用channel.publish(test-message, message)。在messages.map(...)渲染时ably.auth.clientId还能帮你把当前用户是谁显示在消息中。在线状态usePresence 与 usePresenceListenerPresence 特性用于感知频道内谁在线以及各自的状态数据。Ably React 把它拆成了两个互补的 HookusePresence让自己进入频道的 Presence 集合并提供updateStatus用来随时更新自己的状态数据usePresenceListener订阅整个频道 Presence 的变化持续返回最新的在线成员列表presenceData。README 中的组合示例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 / ); }其中presenceData数组中的每一项都带有clientId与data字段——前者标识在线成员身份后者是该成员通过updateStatus写入的状态载荷。示例代码用String(msg.data ?? )安全兜底避免成员状态为空时渲染出 undefined。当有客户端加入或离开频道时例如新用户打开页面、关闭标签页presenceData会自动更新列表随之实时增删。真实工程里updateStatus(hello)的调用发生在 app/home-client.tsx 的 Update status to hello 按钮上同时presenceData被渲染为 Present Clients 列表每行形如clientId: hello——多开几个浏览器标签页你就能直观看到各标签页的随机clientId陆续上线并出现在彼此的画面中。进阶能力由服务端代为发布消息useChannel的channel.publish是从当前客户端自己的身份发消息。如果想让服务端以受控身份向频道推送消息例如触发系统通知、鉴权后的业务事件示例还提供了一个名为 Send A Message From the Server 的按钮。其实现是客户端把一条ProxyMessage{ sender: string }POST 给 Route Handler// 客户端侧home-client.tsx 中 fetch(/api/send-message, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(proxyMessage), });对应的服务端处理器位于 app/api/send-message/route.tsimport * as Ably from ably; import { type NextRequest, NextResponse } from next/server; import type { ProxyMessage, TextMessage } from ../../../types; export async function POST(request: NextRequest) { if (!process.env.ABLY_API_KEY) { return NextResponse.json( { error: Missing ABLY_API_KEY environment variable }, { status: 500 }, ); } const body (await request.json()) as ProxyMessage; const client new Ably.Rest(process.env.ABLY_API_KEY); const channel client.channels.get(some-channel-name); const message: TextMessage { text: Server sent a message on behalf of ${body.sender}, }; await channel.publish(test-message, message); return NextResponse.json({ ok: true }); }与客户端走 WebSocket 不同这里使用的是Ably.Rest的 REST 通道服务端用 API Key 实例化Ably.Rest通过client.channels.get(some-channel-name)获取频道句柄并调用publish(test-message, message)把消息以服务端的合法身份投递到与聊天区相同的频道与事件上。由于所有端多个浏览器标签页的 WebSocket 连接 服务端 REST 通道订阅的是同一个some-channel-name频道的test-message事件这条由服务器代发的消息会同样出现在所有在线客户端的消息列表中从而验证不同发布路径、同一实时频道的互通性。此外注意类型导入路径../../../types是相对该 Route Handler 文件位置app/api/send-message/的工程内引用类型源同为 types.d.ts。常见问题与实践建议连接一直停在 Connecting to Ably...最常见原因是.env.local缺失或ABLY_API_KEY填错导致/api/createTokenRequest返回 500。可先直接访问该接口路径确认响应是否包含tokenRequest字段。密钥绝对不能进入客户端代码示例用authUrl走 Route Handler 签发 Token是推荐做法。请勿把ABLY_API_KEY暴露给浏览器。Provider 放置位置影响首屏体验优先将实时 Provider 包裹在最小必要子树外围见 app/home-client.tsx让静态页面骨架先行渲染。命名空间约定示例统一使用频道some-channel-name、事件名test-message实际项目中建议按业务域规划频道名并保持客户端、服务端、Presence 三处引用一致。进一步阅读Ably React Hooks 的完整 API含useChannel、usePresence、usePresenceListener之外的能力可参考 Ably 官方 React 集成文档消息模型与 Presence 语义也可对照 Ably 文档的实时消息与在线状态章节深入理解。若要了解 Ably 在 Next.js 之外更多官方示例的用法可直接检索本仓库examples/目录下的相关工程。至此你已经完整掌握了一个最小但五脏俱全的 Next.js Ably 实时应用的全部实现链路从密钥配置、Token 鉴权、Provider 注入到消息的订阅/发布、Presence 的读写再到服务端 REST 通道代发消息。照着上述代码与步骤即可把同样的模式迁移进你自己的聊天、协作编辑或实时看板类应用。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考