useQuery is a subscription. Some features need more than basic sync: presence, optimistic updates, collaborative editing. The patterns:
Pattern: presence
“Show who’s currently viewing this document.”convex/schema.ts
convex/presence.ts
heartbeat every 10 seconds; list shows who’s active in the last 30s.
A scheduled function cleans up stale rows nightly.
Pattern: collaborative editing
Two approaches:Operational transformation (OT)
Each edit is a transform. Server reconciles concurrent edits. Complex; use a library (Yjs, Automerge).
Last-write-wins per field
Each field is updated independently; last write wins. Simple; works for forms and structured docs.
Pattern: live cursors
Same shape as presence, but addscursor: { x, y } updated on mousemove (debounced to 30 fps).
Pattern: notifications stream
Anotifications table indexed by_user_and_read. Component subscribes via useQuery for the unread set. Reactive sync delivers new notifications instantly.
Limits
- ~10,000 concurrent subscribers per query (soft).
- Per-document presence with 1000+ users requires sharding or a different store.
Tips
Related
Real-time sync
The basics.
Real-time chat recipe
A worked example.
Multiplayer game recipe
Heavier real-time use.
