L’essentiel en 30 secondes
- Realtime offre trois fonctions : Broadcast (messages entre clients ou émis par la base), Presence (qui est connecté) et Postgres Changes (écoute d'une table).
- Supabase recommande Broadcast déclenché par trigger pour diffuser les changements de la base ; Postgres Changes reste plus simple mais vérifie la RLS pour chaque abonné.
- Avec Postgres Changes, la RLS ne s'applique pas aux DELETE, et l'ancien enregistrement n'est envoyé qu'avec replica identity full.
- Les limites dépendent du plan : 200 connexions simultanées en Free, 500 en Pro, 10 000 sans plafond de dépenses.
Supabase Realtime pousse des messages aux clients connectés par WebSocket, avec trois fonctions : Broadcast (messages à faible latence entre clients, ou émis par la base), Presence (qui est connecté) et Postgres Changes (écoute des INSERT, UPDATE et DELETE d'une table)1. Pour diffuser les changements de la base, Supabase recommande aujourd'hui Broadcast, déclenché par un trigger, pour des raisons d'échelle et de sécurité ; Postgres Changes reste la voie la plus simple pour une application modeste2.
Pour la vue d'ensemble de Supabase, voir notre tour d'horizon de la plateforme Supabase. Pour situer Realtime parmi les autres composants de la plateforme, voir l'architecture de Supabase.
Broadcast ou Postgres Changes : lequel choisir ?
| — | Postgres Changes | Broadcast depuis la base |
|---|---|---|
| Mise en place | Ajouter la table à une publication | Trigger + policies RLS sur realtime.messages + canal privé |
| Contrôle d'accès | RLS de la table vérifiée pour chaque abonné (sauf DELETE) | Policies sur le canal (topic) |
| Montée en charge | Une vérification par abonné et par changement, traitement sur un seul fil | Chaque changement envoyé une fois puis diffusé |
| Quand l'utiliser | Prototype, faible nombre d'abonnés | Au-delà d'environ 3 000 abonnés simultanés aux mêmes changements, et par défaut selon Supabase |
Le seuil d'environ 3 000 abonnés et le fonctionnement sur un seul fil (qui rend inutile une instance plus puissante pour ce cas) viennent de la documentation Supabase3.
Comment écouter les changements d'une table avec Postgres Changes ?
Ajoutez la table à la publication supabase_realtime, en SQL ou dans Database > Publications du tableau de bord3 :
alter publication supabase_realtime add table public.messages;const channel = supabase
.channel('messages-conversation-123')
.on(
'postgres_changes',
{
event: 'INSERT', // 'INSERT' | 'UPDATE' | 'DELETE' | '*'
schema: 'public',
table: 'messages',
filter: `conversation_id=eq.${conversationId}`,
},
(payload) => addMessage(payload.new)
)
.subscribe()
// Quand l'écran se ferme
await supabase.removeChannel(channel)Trois comportements à connaître3 :
- par défaut, seul le nouvel enregistrement est envoyé ; pour recevoir l'ancien lors d'un UPDATE ou d'un DELETE, passez la table en
replica identity full; - les événements DELETE ne peuvent être filtrés que si la table est en
replica identity full; - les policies RLS ne s'appliquent pas aux DELETE, puisque Postgres ne peut plus vérifier l'accès à une ligne supprimée.
alter table public.messages replica identity full;Comment diffuser les changements avec Broadcast ?
C'est la méthode recommandée par Supabase. Un trigger appelle realtime.broadcast_changes(), qui publie sur un canal privé ; seuls les utilisateurs autorisés par une policy sur realtime.messages reçoivent les messages2.
-- 1. Autoriser la réception (à restreindre selon votre modèle d'accès)
create policy "authenticated peut recevoir les broadcasts"
on realtime.messages for select to authenticated
using ( true );
-- 2. Fonction appelée par le trigger
create or replace function public.messages_changes()
returns trigger
security definer
language plpgsql
as $$
begin
perform realtime.broadcast_changes(
'room:' || coalesce(NEW.room_id, OLD.room_id)::text, -- topic
TG_OP, TG_OP, TG_TABLE_NAME, TG_TABLE_SCHEMA, NEW, OLD
);
return null;
end;
$$;
-- 3. Trigger
create trigger handle_messages_changes
after insert or update or delete on public.messages
for each row execute function public.messages_changes();await supabase.realtime.setAuth() // nécessaire pour les canaux privés
const channel = supabase
.channel(`room:${roomId}`, { config: { private: true } })
.on('broadcast', { event: 'INSERT' }, ({ payload }) => console.log(payload))
.on('broadcast', { event: 'UPDATE' }, ({ payload }) => console.log(payload))
.on('broadcast', { event: 'DELETE' }, ({ payload }) => console.log(payload))
.subscribe()Le schéma realtime est verrouillé : vous pouvez y créer des policies sur realtime.messages (la RLS y est déjà active), mais pas de tables ni de fonctions. N'ajoutez pas alter table realtime.messages enable row level security à une migration : l'instruction échoue et annule la transaction4. Pour écrire des policies plus fines que using (true), voir notre article sur les policies Row Level Security.
Comment envoyer des messages éphémères entre clients ?
Pour un indicateur « en train d'écrire » ou un curseur partagé, rien ne passe par la base. Par défaut, l'émetteur ne reçoit pas son propre message ; l'option broadcast: { self: true } change ce comportement5.
const channel = supabase.channel(`room:${roomId}:typing`)
channel
.on('broadcast', { event: 'typing' }, ({ payload }) => setTypingUser(payload.username))
.subscribe()
async function notifyTyping(username: string) {
await channel.send({ type: 'broadcast', event: 'typing', payload: { username } })
}Correction : l'ancien exemple utilisait une variable channel jamais déclarée.
Comment savoir qui est en ligne avec Presence ?
const channel = supabase.channel('online-users', {
config: { presence: { key: userId } },
})
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
setOnlineCount(Object.keys(state).length)
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => console.log('arrivée', key, newPresences))
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => console.log('départ', key, leftPresences))
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ user_id: userId, online_at: new Date().toISOString() })
}
})Presence synchronise l'état via le serveur et notifie tous les abonnés à chaque changement : n'appelez pas track() à chaque mouvement de souris, utilisez Broadcast pour cela6.
Quelles limites selon le plan ?
| Limite | Free | Pro | Pro sans plafond de dépenses / Team |
|---|---|---|---|
| Connexions simultanées | 200 | 500 | 10 000 |
| Messages par seconde | 100 | 500 | 2 500 |
| Canaux par connexion | 100 | 100 | 100 |
| Taille d'un message Broadcast | 256 Ko | 3 000 Ko | 3 000 Ko |
| Taille d'un message Postgres Changes | 1 024 Ko | 1 024 Ko | 1 024 Ko |
Valeurs de la documentation Supabase au 30/09/2026. Une limite dépassée se traduit par des erreurs too_many_connections, too_many_channels, too_many_joins ou tenant_events dans le WebSocket et dans les logs Realtime7.
Quelles bonnes pratiques retenir ?
- Fermer chaque canal au démontage du composant (
removeChanneldans le retour duuseEffect). - Filtrer côté serveur (
filter, topics précis) plutôt que de trier côté client. - Écrire des policies RLS simples et indexées : avec Postgres Changes, elles sont évaluées pour chaque abonné.
- Utiliser Presence pour l'état de connexion, Broadcast pour tout ce qui change vite.
Pour intégrer ces abonnements dans des hooks React, voir les hooks React pour Supabase ; pour un projet temps réel complet, notre offre de développement temps réel sur mesure.






Commentaires
Chaque commentaire est relu avant publication, en général sous 24 h ouvrées. Les liens promotionnels ne sont pas publiés.
Aucun commentaire pour l’instant. Une question sur l’article ? Posez-la ci-dessous.