はじめに
Notionのようなドキュメントエディタと、Miroのようなホワイトボードを、同じアプリの中で両方作りたくなったことはないでしょうか。実際にやろうとすると、リッチテキスト側にはProseMirrorやSlate、キャンバス側にはExcalidrawやtldrawといった具合に別々のライブラリを組み合わせることになり、データ形式もUndo/Redoの仕組みも、リアルタイム共同編集の同期も、それぞれ別々に作り込む羽目になります。
BlockSuiteは、この2つの編集体験を同じCRDTベースのドキュメントモデルの上に載せてしまうツールキットです。ノートアプリAFFiNEのエディタ部分を切り出したオープンソースプロジェクトで、1つのdocオブジェクトに対してドキュメント用のエディタとキャンバス用のエディタを自由に差し替えられるのが最大の特徴です。
とはいえ、読むより触った方が早いと思います。ボタンを押すとその場で段落が増える、実際に動くPageEditorを用意したので、先に挙動を見たい方はこちらからどうぞ。
BlockSuiteとは
BlockSuiteは「Content editing tech stack for the web」を掲げるツールキットで、エディタや共同編集アプリケーションを構築するための基盤を提供します。GitHub上では約6,000スターを集め、2026年現在もほぼ毎日コミットが入るペースで開発が続いています。
主な特徴
- CRDT(Yjs)ベースの協調編集 - すべての編集操作がCRDTの差分として扱われるため、リアルタイム共同編集やUndo/Redo、タイムトラベル(編集履歴の巻き戻し)を標準で備えています
- PageEditorとEdgelessEditorが同じdocを共有 - ドキュメント編集用の
PageEditorと無限キャンバス編集用のEdgelessEditorは、同じdocインスタンスをそのまま渡せば両方で開けます。エクスポート時だけ互換なのではなく、実行時に同じデータ構造を共有しています - フレームワーク非依存のWeb Components - エディタ本体はネイティブのWeb Componentsとして実装されているため、React・Vue・Svelteなどどのフレームワークからでも
<page-editor>のように埋め込めます
インストール
npmから@blocksuite/presets・@blocksuite/blocks・@blocksuite/storeの3パッケージを入れれば使い始められます。
npm install @blocksuite/presets @blocksuite/blocks @blocksuite/store
yarn add @blocksuite/presets @blocksuite/blocks @blocksuite/store
公式ドキュメントでは「BlockSuite is still under rapid development」と明言されており、破壊的変更が入ることもあります。本番で使う際はバージョンを固定しておくのが安全です。
BlockSuiteのサンプルを動かす
下のサンプルは、PageEditorに初期テキストを流し込んだうえで、ボタンを押すとdoc.addBlockで新しい段落ブロックを追加します。エディタ部分は実際にクリックして文字を打つこともできます。BlockSuiteでは、画面に見えているテキストとdocの内部状態が常に同期している点を確かめてみてください。
要点は次のとおりです。createEmptyDoc().init()でひな形となるdocを作り、PageEditorに渡すだけで描画されます。
import { createEmptyDoc, PageEditor } from '@blocksuite/presets'
import { Text } from '@blocksuite/store'
const doc = createEmptyDoc().init()
const editor = new PageEditor()
editor.doc = doc
document.body.append(editor)
const paragraph = doc.getBlockByFlavour('affine:paragraph')[0]
doc.updateBlock(paragraph, { text: new Text('Hello World!') })
実際に動かせるものが下です。「段落を追加」ボタンを押すたびに、doc.addBlock('affine:paragraph', ...)が呼ばれてエディタ側の表示が増えていきます。本文中の文字を直接書き換えても、その場で反映されます。
doc.updateBlockで既存ブロックの中身を書き換え、doc.addBlockで新しいブロックを差し込む。この2つのAPIだけで、リッチテキストエディタの状態を外側のコードから自由にコントロールできることが分かります。テンプレート挿入やAIが生成した文章の流し込みも、原理的にはこの延長線上にあります。
基本的な使い方
先ほどのcreateEmptyDocは、内部で何をしているかを隠した便利関数です。実際には次の3ステップでdocが組み立てられています。
SchemaにAffineSchemas(段落・リスト・画像などBlockSuite標準のブロック定義一式)を登録するDocCollectionを作り、Schemaを渡してメタ情報を初期化するcollection.createDoc()でdocを作り、doc.addBlockでページ・ノート・段落のツリーを組み立てる
手動で書くと次のようになります。PageEditorに渡すdocは、この3ステップさえ踏めばcreateEmptyDocを使わずに自作できます。
affine:pageがルート、affine:surfaceがキャンバス要素の置き場、affine:noteがドキュメント本文の入れ物、affine:paragraphが段落です。このブロックツリーの構造を理解しておくと、次の実践編で紹介するEdgelessEditorへの応用もスムーズになります。
実践的なユースケース
EdgelessEditorでホワイトボードを作る
PageEditorをEdgelessEditorに差し替えるだけで、同じdocを無限キャンバスの上で編集できます。createEmptyDoc().init()はaffine:surfaceブロックもあらかじめ含んでいるので、追加設定なしでそのまま渡せます。
画面内のノートをドラッグして動かしたり、左側のツールバーから図形やコネクタを追加したりできます。EdgelessEditorはCanvasElementをHTML5 Canvasに直接描画しているため、拡大縮小してもテキストの視認性が落ちません。
Page/Edgelessモードを1つのdocで切り替える
BlockSuiteのAffineEditorContainerは、modeプロパティに'page'または'edgeless'を代入するだけで、同じdocのまま表示モードを切り替えられます。ドキュメントとホワイトボードを別データとして扱わない、BlockSuiteならではの設計です。
const editor = new AffineEditorContainer()
editor.doc = doc
// 同じdocのまま表示だけ切り替わる
editor.mode = 'edgeless'
editor.mode = 'page'
ボタンを押してpageとedgelessを行き来してみてください。段落として書いた文章が、キャンバス上ではノートブロックとしてそのまま表示されます。データを変換していないので、書いた内容が消えたり形式が崩れたりすることもありません。
ブロックツリーを操作してテンプレートを流し込む
doc.addBlockはユーザー操作からだけでなく、コードから直接呼び出せます。これを使うと、議事録や日報のような定型フォーマットをボタン1つで挿入するテンプレート機能を作れます。
affine:paragraphのtypeに'h1'〜'h6'を指定すると見出しになり、affine:listのtypeを'bulleted'・'numbered'・'todo'と切り替えれば箇条書き・番号付き・チェックリストを使い分けられます。バックエンドから取得したJSONを元にブロックを組み立てれば、外部データのインポート機能にもそのまま応用できます。
まとめ
BlockSuiteは、CRDTベースのドキュメントモデルを軸に、リッチテキストエディタとホワイトボードエディタを同じデータの上で成立させるツールキットです。doc.addBlock・doc.updateBlock・doc.getBlockByFlavourというシンプルなAPIでブロックツリーを操作でき、PageEditor・EdgelessEditor・AffineEditorContainerのどれに渡しても同じdocがそのまま使えます。
ノートアプリや社内ドキュメントツール、あるいはNotion風・Miro風のUIを自作したい場面では、エディタ部分を1から実装するより先に、BlockSuiteでどこまで実現できるか触ってみる価値があります。まずは本記事のサンプルを書き換えて、docの中身がどう変わるかを確かめてみてください。