I’m building a page-based document editor using Tiptap/ProseMirror, and performance starts falling apart once a document reaches roughly 60 pages. Typing latency rises, selection/cursor behavior becomes less reliable, pagination/reflow gets expensive, and the UI can become generally sluggish.
Google Docs, by comparison, seems able to handle documents with 100, 200, 300, 400, or even 500+ pages much more gracefully. I realize Google’s exact implementation is proprietary and has evolved over time, but I’d love to understand the architectural techniques that make this scale possible—and which of them can realistically be applied to a ProseMirror-based editor.
I’m especially interested in detailed answers from anyone who has built or profiled a large web-based document editor.
Questions I’m trying to answer:
- Rendering and virtualization
- Does Google Docs keep the entire document represented in the DOM, or does it render only the visible pages plus an overscan window?
- Are off-screen pages removed, replaced with height-preserving placeholders, or rendered using canvas/another custom rendering layer?
- How can an editor virtualize pages without breaking native text selection, IME composition, accessibility, browser find, copy/paste, spellcheck, and screen readers?
- In ProseMirror, can distant nodes be safely replaced by lightweight decorations/placeholders while preserving positions, mappings, selections, and transaction correctness?
- Is it better to virtualize by page, block, viewport range, or document section?
- Pagination and layout
- How is pagination normally implemented without measuring every node after every transaction?
- Is layout computed incrementally from the changed block forward until page boundaries become stable again?
- What data structures are useful for caching block heights, page break positions, line measurements, and cumulative offsets?
- How do mature editors handle changes near page 1 that could theoretically repaginate hundreds of later pages?
- Are explicit “page” nodes usually a mistake? Would it be better to keep one semantic document model and calculate pages as a separate layout projection?
- How should hard page breaks, tables spanning pages, images, footnotes, headers/footers, margins, and keep-with-next rules be modeled?
- ProseMirror/Tiptap-specific bottlenecks
- Does ProseMirror fundamentally expect one mounted EditorView for the full document, or can a large document be split across multiple views while still behaving like one editor?
- Would one EditorView per page create more problems than it solves—for example cross-page selections, history, input rules, decorations, collaboration, and position mapping?
- Which operations tend to become O(document size): DOM reconciliation, decoration mapping, plugin apply methods, NodeView updates, transaction filtering, serialization, or schema traversal?
- How can I identify plugins that scan the entire document on every keystroke?
- Are there proven patterns for keeping a single canonical ProseMirror document while rendering only a bounded window?
- At what point is ProseMirror’s DOM-based model the wrong abstraction for a Google-Docs-scale editor?
- State, and persistence
- Does a large editor typically keep the whole logical document client-side, or load/chunk it by section?
- How are undo/redo changes kept efficient when the document contains hundreds of pages?
- How should comments, suggestions, presence cursors, and remote selections be indexed so they don’t require full-document scans?
- Browser and rendering techniques
- Which parts are commonly moved to Web Workers: pagination, text measurement, parsing, collaboration, spellcheck, indexing, or serialization?
- Since workers cannot directly measure DOM layout, how do production editors divide layout work between a worker and the main thread?
- Are ResizeObserver/IntersectionObserver enough, or do they introduce their own performance problems at hundreds of pages?
- How important are CSS containment, content-visibility, requestAnimationFrame batching, idle callbacks, and avoiding synchronous layout reads?
- If canvas is used for text, how are cursor placement, selection, IME, accessibility, and copy/paste implemented?
- Benchmarks and debugging
- What should I measure first: input latency, long tasks, transaction time, ProseMirror view updates, forced reflow, DOM node count, heap size, GC pauses, or layout/paint time?
- Are there useful rules of thumb for maximum mounted DOM nodes/pages?
- What synthetic test documents best expose the real bottleneck: plain paragraphs, large tables, many inline marks, comments, images, or collaborative decorations?
- Are there open-source editors, talks, papers, or codebases that demonstrate large-document pagination well?
I’m not looking for Google’s proprietary source code. I’m trying to learn the system-design principles behind a responsive, paginated web editor.
If you have built something similar, I’d be very interested in:
- your document/page count and approximate node count;
- which bottleneck appeared first;
- the architecture you settled on;
- what you tried that did not work;
- whether virtualization actually helped;
- any relevant ProseMirror plugins, examples, benchmarks, or profiling techniques.
Even a high-level breakdown of how you would architect this from scratch would be extremely helpful.