Skip to main content
The @veltdev/nutrient-crdt-react and @veltdev/nutrient-crdt libraries synchronize Nutrient Instant JSON through a Velt CRDT Store backed by Yjs. The PDF remains a viewer resource; annotations, comments, form values, and transient page selections are the collaborative state.

Prerequisites

  • Node.js (v14 or higher)
  • A Nutrient Web SDK license for production use, when required by your Nutrient account
  • A Velt account with an API key (sign up)
  • React (v18 or higher) when using the React wrapper
  • A PDF URL, Blob, ArrayBuffer, or another document input supported by Nutrient
  • Optional: TypeScript for type safety

Setup

Step 1: Install Dependencies

The current base wrapper peer range targets @nutrient-sdk/viewer@^1.17.0. Keep one compatible copy of the viewer and one copy of Yjs in the final application bundle.

Step 2: Create and Load the Viewer

The viewer host must have non-zero dimensions and remain mounted while collaboration is active. position: relative keeps remote overlays aligned with the host.
NutrientCrdtEditor can create the host and load the viewer. When using useCollaboration(), create the viewer yourself:
This effect unloads only when load() resolves after teardown. Once a viewer has been accepted into instance, leave normal cleanup to the coordinated manager-first teardown in Step 14 so the viewer is unloaded exactly once after collaboration releases its listeners.
Use useCDN: true for the package-manager setup. If you self-host Nutrient assets, supply the asset/base URL options required by your Nutrient deployment instead. The collaboration wrapper never calls NutrientViewer.load() in the Other Frameworks path.

Step 3: Setup Velt

Authenticate a user and set a stable document context before creating collaboration. Follow the Velt Setup Docs for the server token endpoint used by authProvider.
Every collaborator opening the same PDF must use the same Velt document ID and wrapper editorId.

Step 4: Initialize Collaboration

Pass the viewer host as overlayContainer so the manager can render page-rectangle selections above the viewer.
NutrientCrdtEditor creates the host, loads Nutrient, attaches collaboration, and unloads viewers that it created:

React Context Provider (Optional)

React applications can expose the same hook value through context:
CollaborationProvider is an alias of NutrientCrdtProvider. Use this instead of calling useCollaboration() in multiple descendants for the same viewer.

Step 5: Monitor Status and Sync

The hook and component expose reactive status, synced, error, and stats values. Subscribe through the manager only when an additional callback is needed:

Step 6: Initial Instant JSON and Reset Behavior

initialContent is seed data for a new, empty collaborative Store. A client joining an existing document receives persisted Instant JSON instead of overwriting it. The manager strips pdfId before persistence because that identifier is local to a particular PDF load.
Use an explicit reset only for an administrator action, test setup, or another destructive workflow:
forceResetInitialContent and forceReset() replace shared annotations, comments, and form values for every collaborator. Do not enable them during normal routing.

Step 7: Flush Viewer Changes

Viewer events schedule debounced exportInstantJSON() calls. Request an immediate flush after a multi-step custom viewer operation or before navigation when your app must wait for persistence:
The default debounce is 120 ms. manager.getStats().lastFlushReason exposes the most recent reason for diagnostics.

Step 8: Work with Document State

Use applyInstantJson(nextInstantJson, 'import') for a full import. Use getInstantJson() or getDocumentState() to read the current normalized snapshot.

Step 9: Publish and Render Remote Selections

The manager automatically listens for supported Nutrient selection events. For custom viewer tools, publish page-relative rectangles explicitly:
Selections are awareness data: they are transient, disappear when a peer leaves, and are not included in Instant JSON or versions. Map rectangle coordinates through the current page, zoom, and scroll transform when your viewer reports a different coordinate space.

Step 10: Save and Restore Versions

Saving flushes the current viewer first. Restoring reapplies the complete stored Instant JSON to the attached viewer.

Step 11: Subscribe to CRDT Events

Step 12: Attach or Replace a Viewer (Alternative)

Initialize without a viewer only when the Store must outlive the viewer:
This is an alternative to passing instance to the factory. Never use both attachment paths for the same viewer.

Step 13: Custom Encryption (Optional)

See setEncryptionProvider() and VeltEncryptionProvider.

Step 14: Clean Up

useCollaboration() unsubscribes its listeners and destroys its manager on unmount. NutrientCrdtEditor also unloads a viewer that it created, but never unloads an external instance. When your application owns the viewer, release collaboration before unloading the host:
viewerAcceptedRef comes from the loading step. It prevents the coordinated cleanup from unloading a viewer whose pending load() promise will instead be handled by the late-load race branch.

Notes

  • Snapshot model: Nutrient Instant JSON is synchronized as snapshots, not character-level PDF operations.
  • Store identity: The manager uses a map Store with content key document and source metadata nutrient.
  • Overlay container: Supply a stable, positioned viewer host so remote page rectangles render in the correct stacking and coordinate context.
  • Viewer capability: Full synchronization requires exportInstantJSON() and an Instant JSON application method supported by the viewer.
  • One manager: Keep one manager per viewer/document pair.
  • Stable IDs: All collaborators must share the same document ID and editor ID.
  • Yjs ownership: Use getDoc(), getMap(), getProvider(), and getAwareness() only as advanced escape hatches. Do not create a second Y.Doc or provider for the same viewer.
  • Production authentication: Use a server-backed React authProvider and never expose privileged server tokens in browser code.

Testing and Debugging

  1. Open the same PDF, Velt document, and Nutrient editorId as two different authenticated users in separate browser profiles.
  2. Add annotations, comments, highlights, and form values from both clients.
  3. Verify remote selection overlays, version restore, reconnection, and persistence after reload.
  4. Flush before navigation, then confirm the last local operation persists.
  5. Destroy and reload the viewer, and separately test the attach-later lifecycle.
Common issues:
  • Viewer does not load: verify the document URL, license, CDN/asset configuration, and host height.
  • Collaboration remains loading: confirm Velt initialization, an authenticated user, and a viewer instance, or use initializeWithoutInstance.
  • Overlay position is wrong: render into the positioned viewer host and transform coordinates for the current page/zoom/scroll state.
  • Existing data resets: remove forceResetInitialContent from normal loads.
Use the VeltCrdtStoreMap debugging interface to inspect Instant JSON state and provider status.

Complete Example

Complete App.tsx
Complete styles.css

How It Works

  1. The host application loads Nutrient, initializes Velt, authenticates a user, and sets stable document identity.
  2. The manager creates one Velt Store, Y.Doc, Y.Map, provider, and awareness instance for the viewer.
  3. Local viewer changes are exported as debounced Instant JSON snapshots; remote snapshots are applied to the attached viewer.
  4. Awareness carries page rectangles and user metadata for transient overlay rendering.
  5. Version operations flush and restore the complete normalized Instant JSON state.
  6. Cleanup releases collaboration listeners and overlays before Nutrient unloads the host.

APIs

React: NutrientCrdtEditor

React: useCollaboration()

CollaborationManager Methods

Exported Types and Helpers

  • CollaborationConfig, AttachInstanceOptions, CollaborationManager, CollaborationStats
  • InstantJson, NutrientDocumentState, NutrientViewerInstance, NutrientStore
  • AwarenessSelection, RemoteSelection, SelectionRect
  • NutrientHighlightInput, NutrientCommentInput, DocumentChangeEvent
  • INSTANT_JSON_FORMAT, createCollaboration(), stripPdfId(), and the Instant JSON helper functions