인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
데이터 및 연구 · SKILL.md

backend-realtime

backend-realtime

Implement real-time features with WebSockets, Supabase Realtime, or Server-Sent Events. Use when "real-time", "live updates", "WebSocket", "notifications", "chat", "presence", "collaborative", "live data", or "instant sync".

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: backend-realtime description: > Implement real-time features with WebSockets, Supabase Realtime, or Server-Sent Events. Use when "real-time", "live updates", "WebSocket", "notifications", "chat", "presence", "collaborative", "live data", or "instant sync". license: MIT

Real-time Features Skill

Degree of freedom: MIXED. Which transport and event [HIGH freedom]; existing-subscription probes and unmount cleanup [LOW freedom — run exactly].

How to reason

  1. Observe — existing channels, sockets, hooks, cleanup
  2. Interpret — new subscription vs reuse vs transport mismatch
  3. Classify — postgres-changes / presence / broadcast / SSE / optimistic
  4. Severity — leaked or duplicate subscription outranks a missing typing indicator

Worked example

Observe: chat mounts useRealtimeMessages twice; no untrack on leave; package.json already has @supabase/supabase-js. Interpret: duplicate INSERT listeners; leftover presence. Classify: one channel hook with unmount cleanup; reuse Supabase Realtime — do not add Socket.io. Verify: one postgres_changes subscription; removeChannel on unmount; no second socket lib.

Self-critique before reporting

  • Existing first — grepped channels/sockets before adding a library
  • Cleanup — every subscribe has unmount removeChannel / close
  • One listener — no duplicate subscription for the same event
  • Right owner — FE↔BE payload mismatch → debug-fe-be-integration; queue/job instead of live push → backend-patterns

Implement live, collaborative features using WebSockets, Supabase Realtime, and Server-Sent Events.

Code examples assume a Next.js App Router + Supabase stack (@/lib/supabase/client, 'use client'). Adapt import paths and row types to the detected stack.

Check existing first [LOW freedom — run exactly]

Before implementing ANY real-time feature, verify:

  1. Check for existing real-time setup:
cat package.json | grep -i "socket\|realtime\|pusher\|ably"
rg "supabase.*channel|useSubscription|WebSocket" --type ts --type tsx
  1. Check for existing patterns:
rg "on\('INSERT'\|on\('UPDATE'\|subscribe\(" --type ts
ls -la src/hooks/use*Realtime* src/lib/realtime* 2>/dev/null
  1. Check Supabase config:
rg "createClient|supabaseUrl" --type ts -l
grep -oh "SUPABASE_[A-Z_]*" .env* 2>/dev/null | sort -u   # names only — never print values

Why: Real-time connections are stateful. Don't create duplicate subscriptions.

Supabase Realtime [HIGH freedom]

Subscribe to Database Changes

The canonical useRealtimeMessages hook (initial fetch + INSERT/DELETE subscription with unmount cleanup) is in references/patterns.md.

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]))
    .subscribe()
  return () => { supabase.removeChannel(channel) }
}, [roomId, supabase])

Presence (Online Users)

  • One channel per room (presence:${roomId}); channel.track(currentUser) only after status === 'SUBSCRIBED'.
  • Read the roster from presenceState() on the sync event; join/leave are for side effects only.
  • Cleanup is channel.untrack() then supabase.removeChannel(channel).

Full usePresence hook: references/patterns.md §Presence.

Broadcast (Custom Events)

  • Hold the channel in a useRef; register .on('broadcast', { event }) handlers once, then .subscribe().
  • Send with channel.send({ type: 'broadcast', event, payload }); broadcast is ephemeral, never a source of truth.
  • Use for cursors and typing; use postgres_changes for anything that must persist.

Full useBroadcast hook and collaborative-cursor usage: references/patterns.md §Broadcast.

TanStack Query + Real-time [HIGH freedom]

  • Keep useQuery as the data source; the realtime channel only calls queryClient.invalidateQueries({ queryKey }).
  • Subscribe to postgres_changes with event: '*' on the table (optional filter); remove the channel on unmount.
  • One hook, useRealtimeQuery(queryKey, queryFn, table, filter?), so every list gets the same behaviour.

Full useRealtimeQuery hook: references/patterns.md §TanStack Query + Real-time.

Optimistic Updates [HIGH freedom]

  • useOptimistic(initial, reducer) plus useTransition; add the optimistic row with pending: true and a crypto.randomUUID() id, then await the Server Action.
  • Render pending rows at reduced opacity; the realtime INSERT or the action result replaces them.
  • Disable the submit button while isPending.
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
  initialMessages, (state, msg: Message) => [...state, msg]
)
startTransition(async () => {
  addOptimisticMessage({ id: crypto.randomUUID(), content, pending: true, /* ... */ })
  await addMessage(content) // Server Action
})

Full Chat component: references/patterns.md §Optimistic Updates.

Server-Sent Events (SSE) [HIGH freedom]

  • Route handler returns a ReadableStream; write data: ${JSON.stringify(x)}\n\n frames with TextEncoder.
  • Headers: text/event-stream, Cache-Control: no-cache, Connection: keep-alive; heartbeat every 30 s.
  • Clean up on request.signal abort (clear timers, controller.close()).
  • Client: new EventSource(url); close in the effect cleanup and on onerror, then reconnect with backoff.
  • Pick SSE for one-way server push without Supabase; pick Realtime when the data already lives in Postgres.

Full route handler and useSSE hook: references/patterns.md §Server-Sent Events.

Typing Indicators [HIGH freedom]

  • Broadcast { event: 'typing', payload: { userId } } on keystroke; ignore your own id on receipt.
  • Add the sender to typingUsers and drop them after 3 s of silence; debounce sends with a timeoutRef.

Full useTypingIndicator hook: references/patterns.md §Typing Indicators.

Validation [LOW freedom — do not skip]

After implementing real-time features:

  1. Connection handling → Reconnects on disconnect, shows status
  2. Error handling → Graceful degradation when real-time unavailable
  3. Memory leaks → All subscriptions cleaned up on unmount
  4. Duplicate subscriptions → No multiple listeners for same event
  5. Optimistic updates → UI feels instant, handles conflicts
  6. Mobile → Works on spotty connections, battery efficient
저장한 Skills