Supabase Realtime : Synchronisation de Données en Temps Réel

Supabase Realtime permet de recevoir des mises à jour en temps réel depuis votre base de données PostgreSQL via WebSockets. Idéal pour les chats, tableaux de bord live, applications collaboratives et notifications instantanées.

Pour les fondations, consultez notre guide complet Supabase. Pour activer les changements sur vos tables, voir créer et structurer sa base de données Supabase. Pour sécuriser les channels avec RLS, voir Supabase RLS. Pour intégrer le Realtime dans React, voir Supabase avec React. Pour intégrer dans Next.js, voir Supabase avec Next.js. Pour déclencher des actions serveur sur les changements, voir Edge Functions Supabase.

Comment Fonctionne Supabase Realtime

Supabase Realtime utilise le système de Logical Replication de PostgreSQL pour capturer les changements (INSERT, UPDATE, DELETE) et les diffuser via WebSockets. Il existe 3 fonctionnalités Realtime :

  • Broadcast : envoyer des messages éphémères entre clients
  • Presence : suivre les utilisateurs connectés en temps réel
  • Postgres Changes : écouter les changements de la base de données

Activer Realtime sur une Table

-- Via SQL
ALTER PUBLICATION supabase_realtime ADD TABLE posts;
ALTER PUBLICATION supabase_realtime ADD TABLE messages;

-- Ou dans Dashboard > Database > Replication

Postgres Changes : Écouter les Changements DB

const channel = supabase
  .channel('posts-changes')
  .on(
    'postgres_changes',
    {
      event: '*', // INSERT, UPDATE, DELETE, ou '*' pour tout
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('Type:', payload.eventType) // INSERT | UPDATE | DELETE
      console.log('Nouveau:', payload.new)
      console.log('Ancien:', payload.old)
    }
  )
  .subscribe()

// Arrêter l'écoute
await supabase.removeChannel(channel)

Filtrer les changements

// Écouter seulement les messages d'une conversation spécifique
const channel = supabase
  .channel('conversation-123')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'messages',
      filter: `conversation_id=eq.${conversationId}`
    },
    (payload) => {
      addMessage(payload.new)
    }
  )
  .subscribe()

Application Chat en Temps Réel

export function ChatRoom({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<Message[]>([])
  const [newMessage, setNewMessage] = useState('')
  const bottomRef = useRef<HTMLDivElement>(null)

  // Charger les messages initiaux
  useEffect(() => {
    supabase
      .from('messages')
      .select('*, profiles(username)')
      .eq('room_id', roomId)
      .order('created_at', { ascending: true })
      .limit(50)
      .then(({ data }) => setMessages(data ?? []))
  }, [roomId])

  // Écouter les nouveaux messages
  useEffect(() => {
    const channel = supabase
      .channel(`room-${roomId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `room_id=eq.${roomId}`
        },
        (payload) => {
          setMessages(prev => [...prev, payload.new as Message])
          bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
        }
      )
      .subscribe()

    return () => { supabase.removeChannel(channel) }
  }, [roomId])

  const sendMessage = async () => {
    if (!newMessage.trim()) return
    await supabase.from('messages').insert({ content: newMessage, room_id: roomId })
    setNewMessage('')
  }
}

Presence : Savoir qui est en Ligne

const channel = supabase.channel('online-users', {
  config: { presence: { key: userId } }
})

channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState()
    console.log('Utilisateurs en ligne:', Object.keys(state).length)
  })
  .on('presence', { event: 'join' }, ({ key }) => {
    console.log(`${key} vient de rejoindre`)
  })
  .on('presence', { event: 'leave' }, ({ key }) => {
    console.log(`${key} vient de partir`)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        user_id: userId,
        username: 'jeandupont',
        online_at: new Date().toISOString()
      })
    }
  })

Broadcast : Messages Éphémères

// Indicateur "est en train d'écrire"
channel
  .on('broadcast', { event: 'typing' }, ({ payload }) => {
    setTypingUsers(payload.users)
  })
  .subscribe()

async function notifyTyping() {
  await channel.send({
    type: 'broadcast',
    event: 'typing',
    payload: { users: [username] }
  })
}

Bonnes Pratiques Realtime

  • ✅ Toujours cleanup les channels dans le useEffect return
  • ✅ Utiliser des filtres pour réduire le volume de données reçues
  • ✅ Activer RLS sur les tables Realtime — les policies s’appliquent aux WS
  • ✅ Utiliser Presence pour les indicateurs de connexion (pas de polling)
  • ❌ Éviter d’écouter des tables très volumineuses sans filtre
  • ❌ Ne pas créer plusieurs channels pour la même table sans raison

👉 Articles du guide Supabase

Cet article fait partie du Guide Complet Supabase 2026. Retrouvez les autres articles :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *