supabase-realtime
Apply when implementing real-time features: live updates, presence, broadcast messages, or database change subscriptions.
When & Why to Use This Skill
This Claude skill facilitates the seamless implementation of real-time features using Supabase. It provides standardized patterns for database change subscriptions, user presence tracking, and client-to-client broadcasting, helping developers build reactive applications with optimized WebSocket management and robust security practices.
Use Cases
- Real-time Chat Applications: Automatically sync and display new messages across all clients as soon as they are inserted into the database.
- Live Presence Systems: Implement 'Who's Online' features to track and display active user status and metadata in collaborative environments.
- Multi-user Collaboration: Use broadcast channels to synchronize low-latency data like cursor positions or live typing indicators without database overhead.
- Dynamic Dashboards: Create live-updating data visualizations that reflect real-time changes in backend database tables.
- Notification Systems: Trigger instant UI alerts or toast notifications based on specific database events like status updates or new assignments.
| name | supabase-realtime |
|---|---|
| description | Apply when implementing real-time features: live updates, presence, broadcast messages, or database change subscriptions. |
| version | 1.0.0 |
| tokens | ~600 |
| confidence | high |
| - https | //supabase.com/docs/reference/javascript/subscribe |
| last_validated | 2025-01-10 |
| next_review | 2025-01-24 |
| tags | [supabase, realtime, websocket, database] |
When to Use
Apply when implementing real-time features: live updates, presence, broadcast messages, or database change subscriptions.
Patterns
Pattern 1: Database Changes Subscription
// Source: https://supabase.com/docs/guides/realtime
import { useEffect } from 'react';
import { supabase } from '@/lib/supabase';
function useRealtimeMessages(roomId: string) {
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
// Initial fetch
supabase.from('messages').select('*').eq('room_id', roomId)
.then(({ data }) => setMessages(data || []));
// Subscribe to changes
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])
)
.subscribe();
return () => { supabase.removeChannel(channel); };
}, [roomId]);
return messages;
}
Pattern 2: Presence (Who's Online)
// Source: https://supabase.com/docs/guides/realtime/presence
const [onlineUsers, setOnlineUsers] = useState<User[]>([]);
useEffect(() => {
const channel = supabase.channel('online-users');
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState();
const users = Object.values(state).flat() as User[];
setOnlineUsers(users);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ user_id: currentUser.id, name: currentUser.name });
}
});
return () => { supabase.removeChannel(channel); };
}, [currentUser]);
Pattern 3: Broadcast (Client-to-Client)
// Source: https://supabase.com/docs/guides/realtime/broadcast
const channel = supabase.channel('cursor-positions');
// Send cursor position
const sendCursor = (x: number, y: number) => {
channel.send({
type: 'broadcast',
event: 'cursor',
payload: { x, y, userId: currentUser.id },
});
};
// Receive cursor positions
channel
.on('broadcast', { event: 'cursor' }, ({ payload }) => {
setCursors((prev) => ({ ...prev, [payload.userId]: payload }));
})
.subscribe();
Pattern 4: All Change Events
// Source: https://supabase.com/docs/reference/javascript/subscribe
const channel = supabase
.channel('table-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
if (payload.eventType === 'INSERT') handleInsert(payload.new);
if (payload.eventType === 'UPDATE') handleUpdate(payload.new);
if (payload.eventType === 'DELETE') handleDelete(payload.old);
}
)
.subscribe();
Anti-Patterns
- No cleanup - Always
removeChannelon unmount - Subscribing to entire table - Use filters to limit data
- No error handling - Handle subscription errors
- Missing RLS - Realtime respects RLS policies
Verification Checklist
- Channel cleanup in useEffect return
- Filters applied to subscriptions
- Initial data fetched before subscribe
- RLS policies allow realtime access
- Error state handling for subscription failures