Supabase Realtime in der Praxis: Drei Modi im Vergleich und kollaborative Apps

Sie starren auf den Bildschirm – „wird getippt …“ blinkt zum siebzehnten Mal, während Sie die Chat-Seite erneut neu laden. Auf der anderen Seite ist Ihr Freund eindeutig online, doch die Nachrichten kommen einfach nicht an. Eine wirklich „Echtzeit“-App zu bauen, ist schwieriger, als man denkt.
Mit WebSockets habe ich viele Fallstricke kennengelernt: Reconnect nach Verbindungsabbruch, Statussynchronisation, Broadcast-Design. Erst mit Supabase Realtime im letzten Jahr wurde mir klar, dass sich vieles davon auslagern lässt. Supabase bietet drei Echtzeit-Modi: Postgres Changes hört auf Datenbankänderungen, Presence verfolgt Nutzerstatus, Broadcast sendet temporäre Nachrichten. Jeder Modus hat seinen Einsatzbereich – die richtige Wahl spart Aufwand, die falsche bereitet Ärger.
In diesem Artikel erklären wir die drei Modi im Detail: wann welcher passt, wie der Code aussieht und wie RLS-Sicherheitsregeln konfiguriert werden. Am Ende kombinieren wir alles in einer vollständigen kollaborativen Chat-App.
Supabase Realtime: Die drei Kernfunktionen im Vergleich
Kurz gesagt: Jeder Modus hat seine Aufgabe – mischen Sie sie nicht wahllos.
Postgres Changes hört auf Datenbankänderungen. Geeignet für Chat-Nachrichten, Benachrichtigungen, Bestellstatus – alles, was persistiert werden muss. Daten liegen in der Datenbank, Clients abonnieren nur die Änderungen.
Presence verfolgt den Online-Status. Geeignet für „Wer ist online?“, Tipp-Indikatoren, Cursor-Positionen in kollaborativen Editoren. Daten werden nicht in der DB gespeichert, sondern im Speicher – nach dem Disconnect sind sie weg.
Broadcast sendet temporäre Nachrichten. Geeignet für Cursor-Bewegungen auf dem Canvas, Live-Positionen in Spielen, temporäre Operationssynchronisation. Unterschied zu Presence: Broadcast eignet sich für hochfrequentes Senden, Presence für Statussynchronisation.
Eine Übersicht:
| Modus | Datenspeicher | Typische Szenarien | Persistenz |
|---|---|---|---|
| Postgres Changes | PostgreSQL | Chat-Nachrichten, Benachrichtigungen, Bestellstatus | Ja |
| Presence | Speicher (Realtime-Dienst) | Online-Nutzer, Tipp-Indikatoren | Nein |
| Broadcast | Keine Speicherung (sofortige Weiterleitung) | Cursor-Bewegungen, Live-Positionen | Nein |
Vielleicht fragen Sie sich: Warum nicht einfach alles über Postgres Changes? Ehrlich gesagt dachte ich am Anfang genauso. Bei einer kollaborativen Whiteboard-App schrieb jede Cursor-Bewegung in die Datenbank – die CPU-Last stieg auf 90 %. Dann wurde mir klar: Manche Daten müssen gar nicht persistiert werden.
Merken Sie sich diese Regel: Verlauf nötig → Postgres Changes. Nur aktueller Status → Presence. Hochfrequent und temporär → Broadcast.
Postgres Changes: Datenbankänderungen abhören
Der häufigste Anwendungsfall: Änderungen in der Datenbank verfolgen. Neue Chat-Nachrichten, geänderte Bestellstatus, neue Likes – alles, was dauerhaft gespeichert werden soll.
Supabase Realtime nutzt PostgreSQL logical replication. Vereinfacht gesagt: Bei jedem INSERT, UPDATE oder DELETE erfasst der Realtime-Dienst die Änderung und pusht sie an abonnierende Clients.
Realtime-Abhörung aktivieren
Zuerst Publication auf Datenbankseite einrichten. Im Supabase SQL Editor ausführen:
-- Realtime publication aktivieren
ALTER publication supabase_realtime ADD TABLE messages;
-- Für Tabellen mit UPDATE- und DELETE-Abhörung REPLICA IDENTITY FULL setzen
ALTER TABLE messages REPLICA IDENTITY FULL;
Warum REPLICA IDENTITY FULL? Standardmäßig protokolliert PostgreSQL nur den Primärschlüssel geänderter Zeilen. Brauchen Sie vollständige Vorher-/Nachher-Daten (z. B. für Audit-Logs), muss diese Option aktiv sein. Achtung: Das erhöht den Schreibaufwand – nur für Tabellen aktivieren, die es wirklich brauchen.
Client-Abonnement-Code
Client-Code am Beispiel von Chat-Nachrichten:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface Message {
id: string
content: string
user_id: string
created_at: string
}
export function useRealtimeMessages(roomId: string) {
const [messages, setMessages] = useState<Message[]>([])
useEffect(() => {
// Zuerst Verlauf laden
const fetchMessages = async () => {
const { data } = await supabase
.from('messages')
.select('*')
.eq('room_id', roomId)
.order('created_at', { ascending: true })
if (data) setMessages(data)
}
fetchMessages()
// Neue Nachrichten abonnieren
const channel = supabase
.channel(`messages:${roomId}`)
.on(
'postgres_changes',
{
event: 'INSERT', // Nur Inserts abhören
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}` // Auf bestimmten Raum filtern
},
(payload) => {
// payload.new enthält die neu eingefügten Daten
setMessages(prev => [...prev, payload.new as Message])
}
)
.subscribe()
// Abonnement aufräumen
return () => {
supabase.removeChannel(channel)
}
}, [roomId])
return messages
}
Einige Details:
- Erst Verlauf, dann Inkrement: Beim Betreten eines Chat-Raums zuerst alte Nachrichten anzeigen, dann neue empfangen. Häufiger Fehler: nur abonnieren, ohne Verlauf zu laden.
- filter-Parameter: Nach Datenbankfeld filtern, irrelevante Nachrichten vermeiden. Syntax:
feldname=eq.wert. - Abonnement aufräumen: Beim Unmount
removeChannelaufrufen – sonst Memory Leak.
RLS-Sicherheitskonfiguration (wichtig!)
Viele übersehen diesen Punkt – er ist aber entscheidend für Produktions-Apps. Standardmäßig folgt Realtime den RLS-Regeln (Row Level Security) der Tabelle. Ohne RLS empfängt der Client möglicherweise nichts – oder Daten, die er nicht sehen sollte.
Beispiel für eine Chat-Raum-Tabelle:
-- Nachrichtentabelle
CREATE TABLE messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id),
user_id uuid REFERENCES auth.users(id),
content text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- RLS aktivieren
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
-- Nachrichten in eigenen Räumen anzeigen
CREATE POLICY "Users can view messages in their rooms"
ON messages FOR SELECT
USING (
room_id IN (
SELECT room_id FROM room_members
WHERE user_id = auth.uid()
)
);
-- Raummitglieder dürfen Nachrichten senden
CREATE POLICY "Room members can insert messages"
ON messages FOR INSERT
WITH CHECK (
room_id IN (
SELECT room_id FROM room_members
WHERE user_id = auth.uid()
)
);
Realtime-Abonnements wenden diese Regeln automatisch an. Nutzer erhalten nur Nachrichtenänderungen, die sie sehen dürfen. Wichtig: Nicht im Frontend filtern – das ist unsicher.
Kommen keine Daten an, zwei Dinge prüfen:
- Ist die Tabelle in der
supabase_realtime-Publication? - Sind RLS-Regeln korrekt konfiguriert?
Presence: Online-Status verfolgen
Presence eignet sich für „Wer ist gerade online?“ – Online-Anzahl im Chat, wer welchen Abschnitt bearbeitet, wer tippt gerade. Diese Daten müssen nicht in der DB liegen, Speicher reicht.
Grundprinzip
Presence funktioniert so: Jeder Client tritt einem Channel bei und ruft track() auf, um seinen Status zu registrieren. Der Realtime-Dienst hält einen Snapshot aller Client-Status und benachrichtigt alle Abonnenten bei Join, Leave oder Update.
Online-Nutzerliste implementieren
Code für eine Online-Nutzerliste im Chat-Raum:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface UserPresence {
user_id: string
username: string
online_at: string
}
export function useOnlineUsers(roomId: string, currentUser: { id: string; username: string }) {
const [users, setUsers] = useState<UserPresence[]>([])
useEffect(() => {
const channel = supabase.channel(`room:${roomId}`, {
config: {
presence: {
key: currentUser.id // Nutzer-ID als Key
}
}
})
channel
.on('presence', { event: 'sync' }, () => {
// Bei Sync alle Online-Nutzer abrufen
const state = channel.presenceState()
// presenceState() liefert { [key]: [UserPresence, ...] }
const onlineUsers = Object.values(state).flat() as UserPresence[]
setUsers(onlineUsers)
})
.on('presence', { event: 'join' }, ({ newPresences }) => {
// Neuer Nutzer beigetreten
console.log('Nutzer beigetreten:', newPresences)
})
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
// Nutzer verlassen
console.log('Nutzer verlassen:', leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// Nach erfolgreichem Abonnement eigenen Status registrieren
await channel.track({
user_id: currentUser.id,
username: currentUser.username,
online_at: new Date().toISOString()
})
}
})
return () => {
supabase.removeChannel(channel)
}
}, [roomId, currentUser])
return users
}
Verwendung:
function ChatRoom({ roomId, currentUser }) {
const onlineUsers = useOnlineUsers(roomId, currentUser)
return (
<div className="flex items-center gap-2 mb-4">
<span className="text-sm text-gray-500">
{onlineUsers.length} online
</span>
<div className="flex -space-x-2">
{onlineUsers.map(user => (
<div
key={user.user_id}
className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm"
title={user.username}
>
{user.username[0]}
</div>
))}
</div>
</div>
)
}
Tipp-Indikator
Presence eignet sich auch für „wird getippt …“. Beim Tippen den eigenen Status aktualisieren, nach kurzer Pause wieder zurücksetzen.
export function useTypingIndicator(roomId: string, currentUser: { id: string }) {
const [typingUsers, setTypingUsers] = useState<string[]>([])
const channelRef = useRef<RealtimeChannel | null>(null)
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
const channel = supabase.channel(`room:${roomId}`)
channelRef.current = channel
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
const typing = Object.values(state)
.flat()
.filter((u: any) => u.is_typing && u.user_id !== currentUser.id)
.map((u: any) => u.username)
setTypingUsers(typing)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: currentUser.id,
is_typing: false
})
}
})
return () => {
supabase.removeChannel(channel)
}
}, [roomId, currentUser])
// Aufrufen, wenn der Nutzer tippt
const setTyping = (isTyping: boolean) => {
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
channelRef.current?.track({
user_id: currentUser.id,
is_typing
})
// Tipp-Status nach 3 Sekunden automatisch löschen
if (isTyping) {
typingTimeoutRef.current = setTimeout(() => {
channelRef.current?.track({
user_id: currentUser.id,
is_typing: false
})
}, 3000)
}
}
return { typingUsers, setTyping }
}
Kleiner Tipp: Nicht bei jedem Tastendruck track() aufrufen – zu häufig verursacht Probleme. Debouncing nutzen oder wie oben den Status nach ein paar Sekunden automatisch zurücksetzen.
Broadcast: Cursor-Tracking und Sofortnachrichten
Broadcast ist der „leichteste“ der drei Modi – Nachrichten werden nicht gespeichert, nicht persistiert, einfach gesendet und vergessen. Ideal für hochfrequente, temporäre Datenübertragung.
Cursor-Tracking-Beispiel
Kollaborative Whiteboards und Multiplayer-Editoren müssen Cursor-Positionen live anzeigen. Broadcast passt perfekt – Cursor-Positionen brauchen keine DB und keinen Verlauf.
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState, useRef } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface CursorPosition {
user_id: string
username: string
x: number
y: number
color: string
}
export function useCursors(canvasId: string, currentUser: { id: string; username: string }) {
const [cursors, setCursors] = useState<Record<string, CursorPosition>>({})
const channelRef = useRef<RealtimeChannel | null>(null)
useEffect(() => {
const channel = supabase.channel(`canvas:${canvasId}`)
channelRef.current = channel
channel
.on('broadcast', { event: 'cursor' }, ({ payload }) => {
// Cursor-Position anderer Nutzer empfangen
if (payload.user_id !== currentUser.id) {
setCursors(prev => ({
...prev,
[payload.user_id]: payload
}))
}
})
.subscribe()
return () => {
supabase.removeChannel(channel)
}
}, [canvasId, currentUser])
// Eigene Cursor-Position senden
const sendCursor = (x: number, y: number) => {
channelRef.current?.send({
type: 'broadcast',
event: 'cursor',
payload: {
user_id: currentUser.id,
username: currentUser.username,
x,
y,
color: getUserColor(currentUser.id)
}
})
}
return { cursors, sendCursor }
}
// Farbe aus Nutzer-ID generieren
function getUserColor(userId: string): string {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
const index = userId.charCodeAt(0) % colors.length
return colors[index]
}
In der Komponente:
function CollaborativeCanvas({ canvasId, currentUser }) {
const { cursors, sendCursor } = useCursors(canvasId, currentUser)
const handleMouseMove = (e: React.MouseEvent) => {
sendCursor(e.clientX, e.clientY)
}
return (
<div className="relative w-full h-full" onMouseMove={handleMouseMove}>
{/* Cursor anderer Nutzer anzeigen */}
{Object.entries(cursors).map(([userId, cursor]) => (
<div
key={userId}
className="absolute pointer-events-none"
style={{ left: cursor.x, top: cursor.y }}
>
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: cursor.color }} />
<span className="text-xs ml-1">{cursor.username}</span>
</div>
))}
</div>
)
}
Broadcast vs. Presence
Broadcast und Presence überschneiden sich teilweise. Der Unterschied:
- Broadcast für hochfrequentes Senden (Dutzende pro Sekunde) – Cursor-Bewegungen, Positions-Sync in Spielen
- Presence für Statussynchronisation (gelegentliche Updates) – „Wer ist online?“, „wird getippt …“
Am besten kombinieren. Broadcast für „bewegliche“ Daten, Presence für „Status“-Daten.
Praxisbeispiel: Kollaborative Chat-App bauen
Jetzt kombinieren wir alle drei Modi in einer echten kollaborativen Chat-App:
- Echtzeit-Nachrichten (Postgres Changes)
- Online-Nutzerliste (Presence)
- Tipp-Indikator (Presence)
Datenbank vorbereiten
Tabellen anlegen:
-- Raumtabelle
CREATE TABLE rooms (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- Raummitglieder-Tabelle
CREATE TABLE room_members (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id) ON DELETE CASCADE,
user_id uuid REFERENCES auth.users(id),
joined_at timestamptz DEFAULT now(),
UNIQUE(room_id, user_id)
);
-- Nachrichtentabelle
CREATE TABLE messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id) ON DELETE CASCADE,
user_id uuid REFERENCES auth.users(id),
content text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- Realtime aktivieren
ALTER publication supabase_realtime ADD TABLE messages;
-- RLS aktivieren
ALTER TABLE messages ENABLE ROW LEVEL Security;
-- RLS: Nur Nachrichten in eigenen Räumen sehen
CREATE POLICY "Users can view messages in their rooms"
ON messages FOR SELECT
USING (
room_id IN (
SELECT room_id FROM room_members WHERE user_id = auth.uid()
)
);
-- RLS: Nur Raummitglieder dürfen Nachrichten senden
CREATE POLICY "Room members can send messages"
ON messages FOR INSERT
WITH CHECK (
room_id IN (
SELECT room_id FROM room_members WHERE user_id = auth.uid()
)
);
Vollständige React-Komponente
Vereinfachte kollaborative Chat-Komponente mit allen drei Modi:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState, useRef, useCallback } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface Message {
id: string
content: string
user_id: string
username: string
created_at: string
}
interface UserPresence {
user_id: string
username: string
is_typing?: boolean
}
export function CollaborativeChat({ roomId, currentUser }) {
const [messages, setMessages] = useState<Message[]>([])
const [onlineUsers, setOnlineUsers] = useState<UserPresence[]>([])
const [typingUsers, setTypingUsers] = useState<string[]>([])
const [inputValue, setInputValue] = useState('')
const channelRef = useRef<RealtimeChannel | null>(null)
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
// Ein Channel für alle drei Funktionen
const channel = supabase.channel(`room:${roomId}`, {
config: { presence: { key: currentUser.id } }
})
channelRef.current = channel
// 1. Postgres Changes: Neue Nachrichten abhören
channel.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}`
},
(payload) => setMessages(prev => [...prev, payload.new as Message])
)
// 2. Presence: Online-Nutzer und Tipp-Status
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
const users = Object.values(state).flat() as UserPresence[]
setOnlineUsers(users)
const typing = users
.filter(u => u.is_typing && u.user_id !== currentUser.id)
.map(u => u.username)
setTypingUsers(typing)
})
// Abonnieren und Presence registrieren
channel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: currentUser.id,
username: currentUser.username,
is_typing: false
})
// Nachrichtenverlauf laden
const { data } = await supabase
.from('messages')
.select('*')
.eq('room_id', roomId)
.order('created_at', { ascending: true })
if (data) setMessages(data)
}
})
return () => supabase.removeChannel(channel)
}, [roomId, currentUser])
const sendMessage = async () => {
if (!inputValue.trim()) return
await supabase.from('messages').insert({
room_id: roomId,
user_id: currentUser.id,
content: inputValue.trim()
})
setInputValue('')
setTyping(false)
}
const setTyping = useCallback((isTyping: boolean) => {
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current)
channelRef.current?.track({
user_id: currentUser.id,
username: currentUser.username,
is_typing
})
if (isTyping) {
typingTimeoutRef.current = setTimeout(() => setTyping(false), 3000)
}
}, [currentUser])
return (
<div className="flex h-full">
{/* Links: Online-Nutzer */}
<div className="w-64 border-r p-4">
<h3 className="font-bold mb-2">Online ({onlineUsers.length})</h3>
{onlineUsers.map(user => (
<div key={user.user_id} className="py-1">
{user.username}
{user.is_typing && <span className="text-sm text-gray-500"> (tippt)</span>}
</div>
))}
</div>
{/* Rechts: Chat */}
<div className="flex-1 flex flex-col">
<div className="flex-1 overflow-y-auto p-4">
{messages.map(msg => (
<div key={msg.id} className="mb-2">
<span className="font-bold">{msg.username}: </span>
{msg.content}
</div>
))}
{typingUsers.length > 0 && (
<div className="text-gray-500 text-sm">
{typingUsers.join(', ')} tippt …
</div>
)}
</div>
<div className="p-4 border-t">
<input
value={inputValue}
onChange={e => { setInputValue(e.target.value); setTyping(true) }}
onKeyDown={e => e.key === 'Enter' && sendMessage()}
placeholder="Nachricht eingeben …"
className="w-full p-2 border rounded"
/>
</div>
</div>
</div>
)
}
Wichtige Design-Entscheidungen
- Ein Channel für alles: Alle drei Funktionen teilen sich einen Channel – weniger Verbindungen.
- Presence-Datenstruktur:
is_typingim gleichen Presence-Objekt. - Aufräumen: Beim Unmount unbedingt
removeChannelaufrufen.
Performance- und Sicherheits-Best Practices
Der Code läuft – vor dem Go-live bleiben noch ein paar Details.
Verbindungsmanagement
Jeder Channel belegt eine WebSocket-Verbindung. Supabase teilt zwar mehrere Channels über eine Verbindung, aber Missbrauch schadet trotzdem.
Empfehlungen:
- Maximal 2–3 Channels pro Seite
- Verwandte Funktionen in einem Channel bündeln (wie im Chat-Beispiel)
- Beim Verlassen sofort
removeChannelaufrufen
// Aufräum-Beispiel
useEffect(() => {
const channel = supabase.channel('my-channel')
channel.subscribe()
return () => {
// Nicht nur unsubscribe – removeChannel verwenden
supabase.removeChannel(channel)
}
}, [])
RLS ist Pflicht
„Frontend-Filter reicht“ funktioniert nicht. Realtime-Abonnements wenden RLS automatisch an – Nutzer erhalten nur Änderungen, die sie sehen dürfen.
Kommen keine Daten an, in dieser Reihenfolge prüfen:
- Ist die Tabelle in der
supabase_realtime-Publication? - Sind RLS-Regeln korrekt? (RLS-Testtool im Supabase Dashboard)
- Ist der Nutzer angemeldet? (Was liefert
auth.uid()?)
Pricing beachten
Supabase Realtime berechnet nach gleichzeitigen Verbindungen: 10 $ pro 1.000 Spitzenverbindungen. Für die meisten kleinen und mittleren Apps reicht das kostenlose Kontingent. Bei vielen gleichzeitig online Nutzern die Channel-Anzahl im Blick behalten.
Offizielles Demo-Projekt: Multiplayer.dev – dort sehen Sie alle drei Modi in Aktion.
Fazit
Die Moduswahl ist einfach:
| Verlauf nötig? | Hochfrequent? | Empfohlener Modus |
|---|---|---|
| Ja | Nein | Postgres Changes |
| Nein | Nein | Presence |
| Nein | Ja | Broadcast |
Bei kollaborativen Apps (Chat, Whiteboard, Dokumenten-Editor) brauchen Sie vermutlich alle drei: Postgres Changes für Nachrichten, Presence für Online-Status, Broadcast für Cursor.
Probieren Sie zuerst Multiplayer.dev aus, um die Wirkung live zu sehen. Dann bauen Sie ein kleines Chat-Demo – der schnellste Einstieg.
Als Nächstes folgt Supabase Storage: Datei-Upload und Bildverarbeitung. Fragen zu Realtime? Hinterlassen Sie gerne einen Kommentar.
FAQ
Was ist der Unterschied zwischen den drei Supabase-Realtime-Modi?
• Postgres Changes hört auf Datenbankänderungen, Daten werden persistiert – ideal für Chat-Nachrichten und Bestellstatus
• Presence verfolgt den Online-Status, Daten liegen im Speicher – ideal für Online-Listen und Tipp-Indikatoren
• Broadcast sendet temporäre Nachrichten ohne Speicherung – ideal für Cursor-Tracking und Live-Positionen
Warum kommen bei meinem Abonnement keine Daten an?
1. Ist die Tabelle in der supabase_realtime-Publication? (ALTER publication supabase_realtime ADD TABLE tabellenname)
2. Sind RLS-Regeln korrekt konfiguriert? (Realtime-Abonnements wenden RLS automatisch an)
3. Hat der Nutzer Berechtigung, die Daten zu sehen? (auth.uid()-Rückgabewert prüfen)
Postgres Changes oder Broadcast – was wählen?
Werden Presence-Daten persistiert?
Welche Auswirkungen hat RLS auf Realtime-Abonnements?
Wie viele Channels sollte eine Seite nutzen?
12 Min. Lesezeit · Veröffentlicht am: 15. Apr. 2026 · Aktualisiert am: 14. Juli 2026
Supabase in der Praxis
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Supabase Storage in der Praxis: Upload, Berechtigungen und CDN-Beschleunigung
Lernen Sie den kompletten Supabase-Storage-Workflow – von Datei-Upload über Berechtigungskonfiguration bis zur CDN-Integration, inklusive RLS-Policies, Nutzerisolation, Smart CDN und Bildtransformation.
Teil 4 von 10
Nächster
Supabase Realtime in der Praxis: WebSocket-Verbindungsmanagement und Reconnect-Strategien
Praxisleitfaden zu Supabase Realtime: WebSocket-Verbindungsmanagement, Reconnect-Strategien und Echtzeit-Abonnements für Postgres Changes. Broadcast, Presence und Postgres Changes – Auswahl und Best Practices für Produktion.
Teil 6 von 10
Ähnliche Beiträge
Supabase Einstieg: PostgreSQL + Auth + Storage als All-in-One-Backend

Supabase Einstieg: PostgreSQL + Auth + Storage als All-in-One-Backend
Supabase-Datenbankdesign: Tabellenstruktur, Beziehungen und Row Level Security – vollständiger Leitfaden


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen