<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SyncPage</title>
    <description>The latest articles on DEV Community by SyncPage (@trysyncpage).</description>
    <link>https://dev.to/trysyncpage</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4144217%2F018879b7-9571-45db-8ec2-83862802df7e.png</url>
      <title>DEV Community: SyncPage</title>
      <link>https://dev.to/trysyncpage</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trysyncpage"/>
    <language>en</language>
    <item>
      <title>Real-time document co-reading in the browser: lessons from syncing page state across readers</title>
      <dc:creator>SyncPage</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:23:25 +0000</pubDate>
      <link>https://dev.to/trysyncpage/real-time-document-co-reading-in-the-browser-lessons-from-syncing-page-state-across-readers-1bej</link>
      <guid>https://dev.to/trysyncpage/real-time-document-co-reading-in-the-browser-lessons-from-syncing-page-state-across-readers-1bej</guid>
      <description>&lt;p&gt;Video calls solved seeing each other. Screen-sharing solved showing each other. But neither solved &lt;em&gt;reading together&lt;/em&gt; — that specific activity where several people need to be in the same document at the same time, each at their own pace, with a shared understanding of where everyone is.&lt;/p&gt;

&lt;p&gt;I've spent the last year building a real-time document collaboration workspace (SyncPage, my side project), and the hardest problems turned out not to be rendering documents or pushing bytes around. They were state problems: whose page is the "current" page, what it means to follow someone, and how to keep annotations meaningful when every reader has a different viewport. Here's what I learned, at the pattern level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core state model: readers, not pages
&lt;/h2&gt;

&lt;p&gt;The first mistake is modeling the document as the shared state. It's tempting: page 12 is page 12, broadcast the current page, done. That works for exactly one use case — a presenter driving a passive audience — and it collapses the moment anyone wants to read independently.&lt;/p&gt;

&lt;p&gt;The model that actually holds up treats &lt;em&gt;each reader's reading position&lt;/em&gt; as the shared state. Every participant has a position: a page index, a scroll offset within the page, and a mode flag (following or free). The "room state" is just the collection of these positions plus the presenter's position. Everything else — who's following whom, who's lost, who's on the pricing table while you're on the appendix — is derived.&lt;/p&gt;

&lt;p&gt;This inversion matters because it makes the two reading modes the same data viewed differently. Follow mode means "my position tracks the presenter's position." Free mode means "my position is mine." Switching modes is a local decision, not a protocol negotiation, which keeps the whole thing robust when connections flap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Presenter sync vs. free reading: the real tension
&lt;/h2&gt;

&lt;p&gt;Here's the design conflict that ate most of our time: presenters want cohesion ("everyone look at page 7"), readers want autonomy ("let me re-read page 4 first"). Both are legitimate. The failure modes are on both sides — lock everyone to the presenter and you get bored, lost readers; let everyone roam and the meeting dissolves into six people reading six different documents.&lt;/p&gt;

&lt;p&gt;The resolution we landed on has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Following is opt-in and visible.&lt;/strong&gt; You see where the presenter is — a quiet indicator, not a hijack — and one click snaps you into sync. Leaving sync is equally cheap. The key property: &lt;em&gt;your reading position is never moved without your consent.&lt;/em&gt; That single rule eliminated an entire class of user frustration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The presenter gets presence, not control.&lt;/strong&gt; The presenter sees where each reader is (page-level granularity — never pixel-level surveillance; reading is private work and the design should say so). If half the room is stuck three pages back, that's a signal to slow down, delivered by the system instead of by awkward interruption.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rejoining sync is forgiving.&lt;/strong&gt; When you click back into follow mode, you land on the presenter's current page, but your free-reading position is remembered. This sounds minor; in practice it's the difference between "I'll just stay synced" and "I'll actually go check that reference," because the cost of wandering drops to zero.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The broader lesson: in collaborative reading, &lt;em&gt;transitions between modes&lt;/em&gt; are the product. The steady states are easy. Getting people gracefully in and out of sync is where the UX lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anchoring annotations to the document, not the screen
&lt;/h2&gt;

&lt;p&gt;Annotations are the second state problem, and it's nastier. A highlight on a PDF page must mean the same thing on a 13" laptop at 100% zoom and a phone in portrait at fit-width. If you store screen coordinates, you've built a bug.&lt;/p&gt;

&lt;p&gt;The pattern that works: anchor every annotation to &lt;em&gt;document space&lt;/em&gt; — page index plus normalized coordinates within the page (fractions of page width/height). Rendering then becomes a pure function of (annotation, viewport): project document coordinates into screen coordinates at draw time. Zoom, resize, rotate — all free, because the stored data never knew about pixels.&lt;/p&gt;

&lt;p&gt;Two subtleties worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documents reflow; pages don't.&lt;/strong&gt; This is why page-index-plus-normalized-position beats text-offset anchoring for our use case. For PDFs and slide decks, the page is a stable unit. For reflowable formats (Word, Excel), you accept that the anchor is approximate and you make the rendering tolerant — show the annotation near its anchor rather than failing to place it. Graceful degradation beats precision here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent annotation needs last-writer-wins at the stroke level, not the document level.&lt;/strong&gt; Two people drawing on the same page simultaneously should both see both drawings. If your sync granularity is "the page's annotation layer," you'll clobber each other. Stroke-level (or shape-level) operations compose; layer-level snapshots don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Presence without surveillance
&lt;/h2&gt;

&lt;p&gt;Real-time collaboration needs presence — you want to know people are there and roughly what they're doing. But there's a line, and document reading makes it visible: knowing &lt;em&gt;which page&lt;/em&gt; someone is on creates shared context; knowing &lt;em&gt;exactly where their eyes are&lt;/em&gt; feels like surveillance.&lt;/p&gt;

&lt;p&gt;We drew the line at page granularity for reading position, and we made presence ambient rather than alerting. No "Alice left page 7" toasts. Just a quiet roster with page numbers. The principle I'd generalize: &lt;strong&gt;broadcast state that helps coordination, withhold state that enables monitoring.&lt;/strong&gt; Your users can feel the difference even if they can't articulate it, and it shows up in whether they actually use the free-reading mode or just stay synced out of self-consciousness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency, conflicts, and the 200-millisecond rule
&lt;/h2&gt;

&lt;p&gt;For page-position sync, you don't need operational transforms or CRDTs — positions are single-writer (each reader owns theirs), so there's nothing to merge. The presenter's position is single-writer too. This is the happy path: last-writer-wins per key, and conflicts are structurally impossible.&lt;/p&gt;

&lt;p&gt;Where it gets interesting is &lt;em&gt;perceived&lt;/em&gt; latency. When the presenter advances a page, followers should feel the page turn as one motion, not as a stutter. The pattern: apply the remote position change immediately on receipt (optimistic), and don't animate toward it — just cut. A 200ms-old truth applied instantly feels more "live" than a 50ms-old truth animated smoothly. Counterintuitive, but test it: animation implies the system is catching up, and catching up implies lag. Instant cuts imply simultaneity.&lt;/p&gt;

&lt;p&gt;For annotations, the reverse: draw the local stroke immediately (zero perceived latency for the author), broadcast the completed stroke, and render remote strokes as they arrive. Nobody notices a 150ms delay on someone else's highlight appearing. Everybody notices a 150ms delay on their own pen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnection identity: the unglamorous hard problem
&lt;/h2&gt;

&lt;p&gt;The least glamorous problem was the most user-visible: what happens when a guest's connection drops mid-session? If they rejoin as a new participant, their annotations become orphaned and the roster shows a ghost. If you over-engineer identity, you've rebuilt accounts — the thing the guest flow was designed to avoid.&lt;/p&gt;

&lt;p&gt;The middle path: session-scoped identity with a reconnection window. A guest's identity (name, color, annotations, reading position) persists for the session and can be reclaimed by the same browser within a grace period. No passwords, no accounts, but also no amnesia. It's a small state machine — active, disconnected, expired — and getting the timeouts right (long enough for a tunnel, short enough that ghosts don't linger) is pure product judgment, not computer science.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Model reader positions, not document state. Single-writer keys eliminate your merge problems before they start.&lt;/li&gt;
&lt;li&gt;Anchor annotations in document space, render in screen space. Never store pixels.&lt;/li&gt;
&lt;li&gt;Make mode transitions — follow to free and back — the cheapest interaction in the product.&lt;/li&gt;
&lt;li&gt;Broadcast coordination state, withhold surveillance state. Page-level presence, nothing finer.&lt;/li&gt;
&lt;li&gt;Apply remote position changes instantly; animate nothing. Draw local input with zero latency.&lt;/li&gt;
&lt;li&gt;Treat reconnection identity as a first-class design problem, not an edge case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Co-reading is one of those problems that looks solved until you watch real people do it — then you notice the "which page are you on?" tax being paid in every meeting, every day. The technology to fix it is unremarkable: positions, anchors, presence, and the discipline to keep the protocol simple. The hard part is respecting the reader.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>collaboration</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
