Skip to content

[Bug] Every keystroke triggers a large Y.Doc update with the full document #1830

@kapil-LH

Description

@kapil-LH

Description

When using SuperDoc with the collaboration module, the updateYdocDocxData() function broadcasts the entire DOCX document to all connected clients after every keystroke. This creates a significant performance bottleneck for real-time collaborative editing at scale.

Observed Behavior

Every keystroke triggers a large Y.Doc update with the full document:

[Y.Doc Update] {
  origin: { event: 'docx-update', user: {...} },
  updateSize: 23799  // ~23KB per keystroke!
}

This happens in addition to the efficient Yjs CRDT delta sync (~200-300 bytes), effectively doubling the sync mechanism and negating the benefits of CRDT-based collaboration.

Impact at Scale

Scenario Bandwidth
1 user typing 60 WPM (~5 chars/sec) ~115KB/second broadcast
10 concurrent editors ~1.15MB/second
100 concurrent editors ~11.5MB/second

This makes real-time collaboration impractical for documents with many concurrent users.

Root Cause Analysis

We traced this to the updateYdocDocxData() function which:

  1. Calls editor.exportDocx({ getUpdatedDocs: true }) - exports the entire document
  2. Stores the result in the Y.Doc's meta map as base64/XML
  3. This triggers a sync to all connected clients

Code Locations (v1.8.x dist)

Main document listener (line ~14678) - Has 1-second debounce:

const initDocumentListener = ({ ydoc, editor }) => {
  const debouncedUpdate = debounce((editor) => {
    updateYdocDocxData(editor);
  }, 1000);  // 1 second debounce
  
  ydoc.on("afterTransaction", (transaction) => {
    if (!hasChangedDocx && transaction.changed?.size && local) {
      debouncedUpdate(editor);
    }
  });
};

Header/Footer update handler (line ~50727) - NO debounce:

const handleUpdate = async ({ transaction }) => {
  // ...
  await updateYdocDocxData(this.#editor, void 0);  // Called on EVERY keystroke!
};
editor.on("update", handleUpdate);

Expected Behavior

The full DOCX sync should:

  1. Be heavily debounced (e.g., 5-10 seconds, not 1 second)
  2. Be optional/disableable for collaboration-only mode where DOCX fidelity isn't needed in real-time
  3. Apply consistently - header/footer updates should also be debounced
  4. Only trigger on save/export rather than during active editing

Feature Request

Please consider adding configuration options:

const superdoc = new SuperDoc({
  modules: {
    collaboration: {
      provider,
      ydoc,
      // New options:
      syncDocxData: false,        // Disable full DOCX sync entirely
      docxSyncDebounce: 10000,    // Or configure debounce interval (ms)
    },
  },
});

Environment

  • SuperDoc version: 1.8.3 (also tested on 1.7.0)
  • Yjs version: 13.6.19
  • @hocuspocus/provider: 2.15.3
  • Browser: Chrome 131
  • Use case: Legal document collaboration with potentially hundreds of concurrent users

Workaround Attempted

We attempted to find a configuration option to disable this behavior but found none in the public API. The debounce interval and sync behavior appear to be hardcoded.

Additional Context

This issue was discovered while debugging a separate cursor position bug (now resolved). The investigation revealed this performance concern which is critical for our production deployment.

SuperDoc version (if relevant)

1.7.0

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions