Blockleaf

Editor and renderer

One document through both packages at once — the write path on the left, what a server would send on the right.

The left pane is the editor. The right pane is @blockleaf/renderer on the same document: no editor, no ProseMirror, nothing that would ship to a reader who is only reading. Type on the left.

left: the editor · right: @blockleaf/renderer on the same document

Release notes

Edit this. Everything below is live — it reads the same document.

  • Drag a block by its handle.

  • Press / on an empty line.

They look identical, which is the point — the renderer writes the same elements and the same attributes the editor does, down to data-id on every block, and a parity test fails if they ever diverge.

// the write path — a client component
'use client';

import { BlockleafEditor } from '@blockleaf/editor';

export const Edit = ({ doc }) => (
	<BlockleafEditor
		defaultValue={doc}
		onChange={save}
	/>
);
// the read path — a server component
import { RenderDocument } from '@blockleaf/renderer/react';

export const Read = ({ doc }) => (
	<div className="blockleaf-editor">
		<div className="blockleaf-editor__content">
			<RenderDocument doc={doc} />
		</div>
	</div>
);

Why not editable={false}

Because it still mounts an editor. editable={false} is right when the same component toggles between reading and writing — a preview button, a draft view the author flips in and out of. It is wrong for a page that will only ever display content, because every reader then downloads ProseMirror to look at a paragraph.

SituationUse
A page that only displays content@blockleaf/renderer
A preview that toggles back into editingeditable={false}
An email, an RSS item, a cached fragmentrenderToHTML

The two wrappers are yours to write

RenderDocument emits the blocks and nothing around them, so the renderer can sit inside your own layout. The stylesheet needs both classes: .blockleaf-editor declares the custom properties, .blockleaf-editor__content is what every prose rule is scoped to. One without the other gets you colours with no typography.

This is also where data-theme works, because you own the element — see theming.

Read more

Server rendering covers overrides, the HTML-string export, heading extraction for a table of contents, and what the renderer deliberately does differently.

On this page