Skip to content

0013. Store long-form content as BlockNote JSON

Context

All long-form content used one Tiptap rich-text editor that stored raw HTML, sanitized only when shown: the wiki page body, the kanban card description, the calendar event description and the data-model rich-text field. Wiki pages and card descriptions needed Notion-style editing: a slash menu, drag handles, nested blocks and checklists. Raw HTML also kept a cross-site scripting surface open at every place it was rendered. The app was pre-release, with only a handful of existing pages.

Options

OptionForAgainst
Extend the Tiptap editor with block featuresOne editor; no migrationBuilding drag handles, slash menu and nesting ourselves
BlockNote, still storing HTMLBlock UX with no storage changeLossy round-trips; keeps the HTML rendering risk
BlockNote, storing its JSON blocksBlock UX out of the box on the same ProseMirror base; lossless; renders as ReactSearch, titles and indexing must extract text from JSON; existing content must be migrated

Decision

Use BlockNote and store its native JSON block array as the source of truth, alongside the existing rich-text editor rather than replacing it. Wiki bodies, kanban card descriptions (whose separate subtasks field became checklist blocks) and a new data-model document field type use blocks. The calendar description and the rich-text field type stay as rich text, because a block canvas is too much for short inline text. Existing HTML bodies were reset to empty rather than converted, since the app was pre-release.

The deciding reason: BlockNote gives the block editing UX on the ProseMirror base already in use, and JSON storage is lossless and removes rendering HTML.

Consequences

  • Block content renders as React, never as injected HTML.
  • Modules that search or derive titles keep a plain-text copy of block content, updated on every write, using a shared helper in @monark/common/blocks.
  • Two editors to maintain, with no conversion between them.
  • BlockNote's default Mantine renderer was incompatible with the React build bundled with Next.js 15.5, so the Ariakit renderer is used; one dependency (lib0) needed a pnpm patch to load in the browser.
  • BlockNote's paid add-ons (multi-column, AI, PDF export) are out of scope.