Realtime collaborative spreadsheet with a local-first document model, CRDT-based syncing, worker-driven evaluation, and a virtualized grid built to stay responsive on 10K+ row datasets.
| Home | Find & Replace | Share menu |
|---|---|---|
![]() |
![]() |
![]() |
- The workbook is modeled as a Yjs document, so cells, sheets, formats, column metadata, and workbook metadata all live in one conflict-tolerant source of truth.
- The web client is local-first: IndexedDB persists workbooks for instant reloads, while Firestore acts as the durable cloud snapshot and sharing registry.
- Realtime collaboration is handled by an Elysia WebSocket service that sends room snapshots on join and incremental Yjs updates after that.
- Presence is intentionally separate from workbook state. Active users, selections, typing state, and viewer/editor access are transported as ephemeral collaboration metadata.
- Formula support is deliberately shallow and defendable for assignment scope: cell refs, ranges, arithmetic, and
SUM,AVERAGE,MIN,MAX,COUNT. - Large-sheet responsiveness comes from sparse cell storage, TanStack row and column virtualization, and a Web Worker that recalculates formulas off the main thread.
flowchart TB
subgraph Client["Next.js Client"]
UI["Dashboard + Spreadsheet UI"]
Store["Zustand Store\nhydration, commands, write state"]
YDoc["Yjs Workbook Doc\ncells, sheets, formats, meta"]
Worker["Formula Worker\ndependency graph + recompute"]
IDB[("IndexedDB\nlocal persistence")]
end
subgraph Realtime["Sync Layer"]
WS["Elysia WebSocket Room\nsnapshot, presence, incremental Yjs fanout"]
Cache[("Room Cache\nserver-side persisted room state")]
end
subgraph Cloud["Firebase"]
Auth["Firebase Auth\nGoogle / session identity"]
Snapshots[("Firestore\nworkbook snapshots")]
Sharing[("Firestore\nsharedWorkbooks policy")]
end
UI --> Store
Store <--> YDoc
Store <--> Worker
YDoc <--> IDB
Store -- initial bootstrap / merge --> Snapshots
Store -- debounced durable sync --> Snapshots
Store -- collaborator identity --> Auth
Store -- snapshot seed + incremental updates --> WS
WS -- snapshot / sync / presence --> Store
WS <--> Cache
WS -- access validation --> Sharing
Auth -- owner token --> WS
- Logical row capacity is
100,000, with the visible window virtualized instead of rendering the full sheet into the DOM. - A 10K+ row CSV is a strong path for this architecture because import stores only populated cells, the grid renders only visible rows and columns, and recalculation stays off the main React thread.
- The live collaboration path and the durable cloud path are intentionally split: Yjs + WebSocket handles low-latency edits and conflict-friendly merging, while Firestore handles debounced persistence, durable restore, and sharing metadata.
- Firestore writes are lease-guarded to reduce remote snapshot clobbering when multiple authenticated clients are open on the same workbook.
- Structural operations like insert, delete, and reorder are supported, but they are heavier than plain editing because they rewrite sparse cell maps and formula references.


