
Supabase Realtime Not Working: Why Am I Not Receive Updates?
Built a chat app, but Realtime subscription is silent? Check the 'Replication' setting. Learn why Postgres WAL is key to Supabase Realtime.

Built a chat app, but Realtime subscription is silent? Check the 'Replication' setting. Learn why Postgres WAL is key to Supabase Realtime.
HTTP is Walkie-Talkie (Over). WebSocket is Phone (Hello). The secret tech behind Chat and Stock Charts.

You changed the code, saved it, but the browser does nothing. Tired of hitting F5 a million times? We dive into how HMR (Hot Module Replacement) works, why it breaks (circular dependencies, case sensitivity, etc.), and how to fix it so you can regain your development speed.

Troubleshooting absolute path import configuration issues in TypeScript/JavaScript projects. Exploring the 'Map vs Taxi Driver' analogy, CommonJS vs ESM history, and Monorepo setup.

How to solve CORS errors during development using proxy configuration and important considerations.

I was building a chat app using Supabase Realtime.
I followed the docs and subscribed: supabase.channel('room-1').on(...).subscribe().
const channel = supabase
.channel('room1')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => {
console.log('New message!', payload);
})
.subscribe();
I sent a message from another window.
The message was inserted into the DB correctly.
But... my console was silent. Nothing happened.
I had to Refresh (F5) to see the new message. Use F5 for Realtime? That's not Realtime!
I checked the Network tab. Websocket connection was successful (101 Switching Protocols).
I checked the Realtime Inspector in the Supabase Dashboard. No messages there either.
"Is the server down? No, it's connected. Is my code wrong?" I checked for typos dozens of times. Nothing was wrong.
The problem wasn't in the code, but in the DB Settings. Supabase (Postgres) defaults to "Do NOT broadcast changes for this table."
Why? If it broadcasted changes for every single table, the server load would be massive. So you must explicitly say, "You are allowed to broadcast this table."
Analogy: The Broadcasting Station (Supabase) has Cameras (Realtime Server) installed. But the News Anchor's (Table) microphone was OFF (Replication Off). No matter how much the anchor shouted (INSERT), no sound went out to the Broadcast (Subscriber).
You fix this in the Supabase Dashboard, not in code.
messages table.Or do it via SQL:
-- Tell Postgres to publish changes for 'messages' table
alter publication supabase_realtime add table "messages";
As soon as I enabled this, logs started flooding my console. "Wow, I forgot to turn on the mic."
Supabase Realtime isn't magic. It reads the Postgres WAL (Write Ahead Log). When DB changes happen, a log is written. The Realtime server tails this log and shoots it over Websocket.
Here's another trap. RLS (Row Level Security).
If messages table has RLS, and a policy says "Cheolsu cannot see Yeonghi's messages."
The Realtime server respects this. If Yeonghi writes a message, Cheolsu's subscription (Channel) will not receive the event. Supabase filters it out automatically.
So if "Replication is on, but no event?", check your SELECT RLS policy.
If you can't see the data, you won't be notified about it either.
Sometimes you get an UPDATE or DELETE event, but the old data field is empty.
Postgres only logs changed columns by default for performance.
If you need the entire row data when a row is deleted (not just the ID), change the table setting:
ALTER TABLE "messages" REPLICA IDENTITY FULL;
This logs all column data on change. (Slight DB performance cost).
In production, you should show "Connected/Disconnected" state in the UI.
const channel = supabase.channel('room1')
.on('postgres_changes', ...)
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
console.log('🟢 Connected');
setIsConnected(true);
}
if (status === 'CHANNEL_ERROR') {
console.log('🔴 Connection Failed');
setIsConnected(false);
}
if (status === 'TIMED_OUT') {
console.log('🟡 Timeout (Retrying...)');
}
});
Especially on mobile (Flutter/React Native), connections drop when the app goes background. Detecting this and showing "Reconnecting..." is essential UX.
Realtime isn't just for DB changes. Presence tracks who is connected. It shares state between clients via WebSocket (Memory-based, No DB access).
const channel = supabase.channel('room-1');
channel
.on('presence', { event: 'sync' }, () => {
const newState = channel.presenceState();
console.log('Online Users:', newState);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// I am here!
await channel.track({ user_id: 'user_123', status: 'online' });
}
});
Use this for "User is typing..." features.
Building a collaborative whiteboard? You need to show others' mouse cursors.
If you INSERT mouse coordinates into the DB 60 times a second, your DB will explode in 1 second.
Supabase Realtime supports Broadcast. It bypasses the DB entirely and sends data client-to-client.
// On mouse move
channel.send({
type: 'broadcast',
event: 'cursor-pos',
payload: { x: 100, y: 200 }
});
It's volatile and fast. Perfect for games or cursors. (Do NOT use Postgres Changes for this!)
Problem: WiFi drops in the subway and reconnects 5 seconds later. Supabase SDK reconnects automatically, but messages sent during those 5 seconds are lost forever. The user has no idea they missed part of the conversation.
Challenge:
Whenever SUBSCRIBED status returns, Fetch data strictly AFTER the last received ID.
/* Pseudo Code */
let lastReceivedId = 0;
channel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// 1. Check last known state
if (lastReceivedId > 0) {
// 2. Fetch missing data since disconnect
const missed = await fetchMessages({ id_gt: lastReceivedId });
mergeMessages(missed);
}
}
});
channel.on(..., (payload) => {
lastReceivedId = payload.new.id;
updateUI(payload.new);
});
Without this "Gap Filling" logic, your chat app is fragile.
Behind Supabase Realtime sits the postgres_output logical decoding plugin.
It converts WAL entries into JSON.
Enabling Replication in the dashboard runs:
CREATE PUBLICATION supabase_realtime FOR TABLE messages;
Architectural Considerations:
REPLICA IDENTITY FULL increases WAL size significantly. Use it only when you absolutely need the old record data (e.g., needed to delete a file associated with the deleted row).Q: Can't I just use a Postgres Trigger to call an API? A:
Use Realtime for Chat. Use Triggers (calling Edge Functions) for "Welcome Email on Signup".