Realtime: Messages Not Arriving Troubleshooting
Last edited: 10/14/2026
你的通道订阅了某些内容,结果发生了变化,但客户端从未收到通知。没有错误,没有提示。这份指南是一个快速参考,涵盖了主要原因:连接问题、广播、Postgres 变化和 Presence。
🌐 Your channel subscribes, something changes, and the client never hears about it. No error, silence. This guide is a quick-reference across the main causes: connection problems, Broadcast, Postgres Changes, and Presence.
如果你的通道从未订阅过,先从连接问题开始。如果连接正常,而问题是某些特定事件没有显示,就直接跳到你正在使用的功能。
🌐 Start with Connection problems if your channel never subscribes. If it connects fine and the problem is that specific events aren't showing up, skip ahead to whichever feature you're using.
连接问题 #
🌐 Connection problems
在你调用 .subscribe() 之后,看看有没有 CHANNEL_ERROR、TIMED_OUT 或 CLOSED:
🌐 Look for CHANNEL_ERROR, TIMED_OUT, or CLOSED right after you call .subscribe():
channel.subscribe((status, err) => { console.log(status, err)})- 检查你的项目没有被暂停,并且实时功能已启用。这个开关(“启用实时服务”)在 实时 → 设置 下。查看 实时设置。
- 在私有通道上,你需要一个允许你角色加入的 RLS 策略。到
realtime.messages的 Realtime → Policies 查看。有关策略如何工作的说明,请参见 Realtime Authorization。慢速策略也可能导致加入超时而不是直接失败,所以除了确认策略是否授予访问权限,也要检查策略本身的性能。 - 坏的 JWT 是一个常见原因。不是过期的,包含
role和exp声明,如果你使用第三方认证服务,它的 JWKS 端点需要可访问。解码 token(jwt.io 或atob(token.split('.')[1]))可以直接查看声明。在.subscribe((status, err) => ...)里记录第二个参数会显示实际的拒绝原因,而不是让你猜。更多细节:通道级系统错误。 - 在你的应用前面用反向代理或自定义 SSL 终止?确保它转发
Upgrade和Connection头,否则 WebSocket 握手根本无法完成。 - 客户端需要每至少 25 秒发送一次心跳,否则连接会超时(重新连接)。所以在遇到意外的
CLOSED/TIMED_OUT时应该重新连接,而不是把它们当作严重错误:记录状态回调,这样你就能区分预期断开和真正的故障。有一个注意事项:如果你特别看到TIMED_OUT,那通常是因为 Node.js 版本和你的realtime-js版本不匹配,而不是空闲超时问题。参见 实时连接出现TIMED_OUT错误。 - 你可能正在触及你所使用计划(
too_many_connections、too_many_joins)的连接数或加入率限制。请查看仪表板中的项目使用页面,或者直接在err中查找错误代码。计划编号:实时限制。相关:实时:“并发峰值连接”配额。
来自客户端的广播 #
🌐 Broadcast from the client
这适用于用 channel.send({ type: 'broadcast', ... }) 发送的消息。
🌐 This applies to messages sent with channel.send({ type: 'broadcast', ... }).
- 主题名称在发送者和每个监听者(
supabase.channel('room-1'))上必须完全匹配。拼写错误、大小写不同,或者双方构建的ID不同都不会报错,但会悄无声息地失败。如果卡住了,可以在双方记录主题字符串并对比,或者启用客户端和服务器日志来查看实时服务器实际接收到的内容:使用 Logger 和日志级别调试实时。 - 私有通道?你需要在
realtime.messages上为连接角色设置针对extension = 'broadcast'的 RLS 策略:一个insert策略用于发送,一个select策略用于接收。缺少select策略的话,发送可以成功,但不会有任何返回。查看 实时 → 策略,并参考 实时授权:广播 获取一个可行的例子。 - 这里的速率限制和连接相同(每秒消息数、通道限制)。使用相同的页面,相同的 实时限制 参考。
从数据库广播 #
🌐 Broadcast from the database
这适用于 realtime.send() 和 realtime.broadcast_changes()。
🌐 This applies to realtime.send() and realtime.broadcast_changes().
- 检查你的 Postgres 日志是否有
WarnSendingBroadcastMessage警告。realtime.messages按天分区,而这个分区只有在有客户端通过 WebSocket 连接后才会创建。在这之前进行广播,或在客户端长时间不连接后进行广播,插入就会因为这个警告而失败。先连接一个客户端通常可以解决问题。背景信息:实时:WarnSendingBroadcastMessage和 从 Postgres 广播。 - 触发器有触发吗?在里面加一个
raise notice然后去 Logs → Postgres Logs 查看。没日志说明触发器没正确连接:表错了、事件错了,或者根本没创建。这完全是 Realtime 之外的问题。 - 检查一下
is_private(realtime.send()的最后一个参数,或者realtime.broadcast_changes()上的相应标志)是否与客户端通道的config.private设置匹配。它是主题本身的公开/私有标志,和上面的 RLS 检查是分开的。如果不匹配,即使触发器触发并且主题名称正确,客户端也永远收不到消息。参见 Broadcast。
postgres_changes#
INSERT和UPDATE事件的行级安全性控制交付。postgres_changes只会发送订阅角色可以select的行。使用订阅角色的凭证运行同样的查询,而不是在 SQL 编辑器中以postgres运行(默认会绕过 RLS):如果结果为空,就说明问题出在 RLS。为了测试,可以添加一个宽松的查询策略,确认问题后再收紧。DELETE事件完全不受 RLS 过滤,因为 Postgres 没办法检查已经删除行的访问权限。相关阅读:与 Postgres 更改的交互。- 再次核对你的筛选条件,看看实际变更的行:
schema、table、event(INSERT/UPDATE/DELETE/*),以及任何.eq()/.in()筛选条件。完整列表请参见 可用筛选器。 - 无论模式如何,该表都需要包含在
supabase_realtime发布中。路径是 数据库 → 发布。如果它不在public中,也有单独的要求。将select权限授予订阅者访问令牌中的角色:登录用户使用authenticated,匿名客户端使用anon,如果你使用自定义 JWT,则使用其他角色(比如grant select on "your_schema"."your_table" to authenticated;)。这部分在仪表板上没有开关。详情见:私有模式。 - 当以上方法都没有任何结果时,把订阅精简到最低限度:不使用过滤器,
event: '*'。如果这仍然没有触发,那么问题可能出在发布或RLS,而不是过滤器语法。
一旦你排除了基本问题,实时:Postgres 变更故障排除 会更深入地探讨这个具体情况。
🌐 Once you've ruled out the basics, Realtime: Postgres Changes Troubleshooting goes deeper into this exact scenario.
存在 #
🌐 Presence
- 你代码库里的某些东西可能在通道配置上设置了
presence: { enabled: false }。值得搜索一下。配置形状参考:phx_join 配置。 - 私有通道?你需要在
realtime.messages上为连接角色设置针对extension = 'presence'的 RLS 策略:一个select策略用于接收在线状态更新,一个insert策略让channel.track()发布你自己的状态。缺少任意一个都可能会悄悄丢失在线状态,即使通道订阅本身成功。查看 实时授权:在线状态 获取一个可用示例。 channel.track()需要等待SUBSCRIBED:Presence 指南里的每个例子都是这样安排的,提前调用它不会可靠地注册。记录状态回调,并确认只有在SUBSCRIBED出现后track()才会触发。查看 Sending state 获取标准模式。
还卡住吗? #
🌐 Still stuck?
实时故障排除条目也涵盖相关问题:TIMED_OUT 连接、通道过多、速率限制、日志调试。
🌐 The Realtime troubleshooting entries cover related issues too: TIMED_OUT connections, too many channels, rate limits, logger debugging.