I built a WordPress writing editor for Minn Admin that is one vanilla JavaScript file, no build step, no React. It stores native Gutenberg block markup. Complex layouts survive round trips as atomic islands. The official block editor is a multi-megabyte platform with a Redux-style data layer, a custom rich text model, and hundreds of registered blocks.

Those two facts should not be able to sit next to each other. Either the block editor is overbuilt, or my lightweight one is a toy. I audited both architectures to find out which.

The short answer is neither. They are different products that happen to be both called an editor.

~4.6MB
Core editor JS packages (min, summed)
~1.3MB
Minn Admin entire SPA (unminified)
109
Core block directories under wp-includes/blocks
16
Blocks Minn edits as live prose

Those numbers are not a dunk on Gutenberg. They are a map of what each editor decided to own.

People collapse three jobs into “the editor.”

When someone says “the block editor is too heavy,” they are usually comparing apples to a content operating system. Gutenberg is not one product. It is three jobs stacked on one React runtime.

Three jobs people call "the WordPress editor"
Writing (prose, lists, images, paste, focus)
Classic still wins installs
Layout (columns, groups, covers, patterns)
Gutenberg + block suites
Site building (FSE, templates, global styles)
Gutenberg only

Classic Editor still holds multi-million active installs years after Gutenberg shipped. That is a standing vote for the writing job. Not a vote against blocks as a storage format. A vote against treating every post edit like a layout session.

Minn Admin targets the writing job on purpose. Gutenberg owns layout and site building on purpose. The architecture question is not “which is better.” It is whether a lightweight writing surface can store Gutenberg markup without becoming Gutenberg.

Gutenberg is huge because of five intentional bets.

I started with the official editor, not because I wanted a takedown, but because the size has to be explained before you can decide what a smaller editor is allowed to refuse.

Architecture bets that made the block editor heavy
UNIVERSAL
Everything is a block
Posts, widgets, navigation, template parts, patterns, theme styles. One registration model for the whole CMS. A writing surface only needs a handful of types. A universal model needs hundreds plus infinite third-party ones.
DetectAsk: does this feature need the universal model, or only the writing subset?
SAVE()
Client-owned serialization
Each block’s JavaScript save() is the source of truth for stored HTML. On load, Gutenberg re-runs save() and validates against the stored markup. Mismatch becomes recovery UI. Instant client authoring for any registered block. Permanent validation fragility when plugins update.
DetectIf you cannot run a block’s save(), you cannot regenerate its HTML safely.
PLATFORM
A React platform, not a form
Data stores, SlotFill, components, block library packages, core-data entity CRUD. Third-party blocks are React applications that must run inside that shell. Once you accept that contract, staying small is no longer an option.
DetectCount the packages required before a blank post canvas paints.
CANVAS
Nested interactive layout
Groups, columns, covers, query loops, navigation. Hierarchical selection, multi-block drag and drop, list view sync, parent and ancestor constraints, live style controls. That is page-builder territory. Classic never tried it. Gutenberg did on purpose.
DetectIf the feature needs a hierarchical canvas, contenteditable prose will not carry it.
FSE
Site building charter
Templates, template parts, global styles, pattern overrides. The block editor grew into WordPress’s site construction runtime. That charter alone justifies weight a writing tool should never carry.
DetectSeparate the post editor product from the site editor product before you judge either.

The load-bearing technical choice is client-owned serialization. Gutenberg assumes the editor that wrote a block can re-save it. That is how nested design systems work. It is also why every static third-party block is effectively a mini application with its own save pipeline.

On a stock WordPress 7 install I summed the major minified packages that typically load for a post editor session. Roughly 4.6 megabytes before a theme or plugin adds its own React blocks. block-library alone is about 1.1MB minified. The whole wp-includes/js/dist tree on disk is around 28MB.

That is the cost of being WordPress’s content OS. It is not accidental bloat. It is the price of the bets above.

Minn’s bet is the opposite of those five.

Minn Admin is a standalone admin SPA at /minn-admin/. Vanilla JS, REST, no build step. Classic wp-admin stays available. I wrote about the larger admin project in 3 Failed WP-Admin Projects, Let’s go for the 4th! This post is only about the editor inside it.

Minn Admin editor with this draft open: the stats dashboard block renders as an atomic island above the prose
Writing this very post in Minn Admin. The stats dashboard above is an anchor block rendered as an atomic island.

The editor decision is written down in the repo as hybrid by design:

  • Own the writing use case. Paragraphs, headings, lists, quotes, code, images, tables, the occasional embed.
  • Store native Gutenberg markup. No proprietary format. Open any post in the block editor forever.
  • Refuse layout parity. Columns, groups, covers, FSE, and full block parity are a never-build list, not a backlog.
  • Escape hatch, not foundation. Gutenberg is one click away for layout work. Minn is not an iframe of post.php.

The safety model is the whole trick. On load, content is classified as classic, blocks, or locked. In blocks mode the raw post is tokenized into top-level segments with a byte-identity reassembly check. If reassembly fails, the body goes locked and content is never sent on save.

Simple blocks with only allowlisted attributes become live contenteditable prose. Everything else becomes an atomic island: a non-editable card that keeps the original markup verbatim and splices it back unchanged on save.

Unknown content is opaque on purpose
In Gutenberg, unknown or invalid blocks become a recovery problem. In Minn, unknown blocks become islands. You can delete them, inspect attributes when the schema allows, edit generic text runs, swap images, and preview with real front-end CSS. You cannot redesign their layout. That refusal is what keeps the lightweight model honest.

Here is the pipeline in plain terms.

post_content  (native Gutenberg markup)
      |
      v
editorModeFor()  →  classic | blocks | locked
      |
      v
tokenizeBlocks()  - top-level split + byte-identity check
      |
      +-- SIMPLE_BLOCKS + EDITABLE_ATTRS  → contenteditable prose
      +-- PASSTHROUGH_BLOCKS              → live DOM + attrs parked on the element
      +-- everything else                 → atomic island (raw markup verbatim)
      |
      v
serializeToBlocks(body, islands)
  prose rewritten; islands spliced unchanged

The allowlists grow one proven attribute at a time. A simple block carrying an attribute the serializer cannot reproduce from the DOM becomes an island instead of a silent data loss. First Minn save may normalize inter-block whitespace. After that, complex layouts stay byte-stable through edit cycles.

The question I wanted answered was about feature parity.

Not “can Minn edit a paragraph.” It can. The interesting question was whether a lightweight architecture could chase Gutenberg feature for feature, given enough time, without adopting Gutenberg’s runtime.

Claude Code Session
Austin
Audit Minn Admin’s editor against Gutenberg. Classic still has a huge install base. Minn is lightweight but stores block markup. Review the architecture decisions behind the official block editor. Could Minn reach the same feature parity with a much lighter framework?
Audit
Full parity with Gutenberg means reimplementing client save() for every block, nested layout canvas, platform APIs, and eventually FSE. That is not a lighter framework. That is Gutenberg. Writing parity with safe layout coexistence is a different product, and Minn’s island model is already built for it.
Austin
Where does the lightweight model hit a hard wall?
Audit
Static third-party blocks whose HTML is produced only by their editor JavaScript. Nested layout authoring. Design-system controls that rewrite classes and inline styles through React. Anything that requires re-running a plugin’s save(). Islands can display and preserve those blocks. They cannot redesign them without hosting that runtime.

Writing parity is achievable. Layout parity is a different product.

I split the feature map the same way the architecture split.

WRITING
Where a lightweight editor can match or beat Gutenberg

Paragraphs, headings, lists, quotes, code, images with captions, tables, paste cleanup from Word and Docs, status-aware autosave, post locking that shares core’s _edit_lock, focus mode, outline, find and replace, markdown typing, crash recovery. This is the job Classic users never stopped wanting. Minn already treats it as the product, with Playwright suites on the hard browser behaviors.

LAYOUT
Where the lightweight model correctly stops

Nested groups and columns as a live canvas. Cover blocks as design tools. Pattern authoring. Block style UIs that regenerate markup through plugin React. Full Site Editing. These need hierarchical selection, client serialization, and a design-system shell. Building them “lightly” usually means smuggling Gutenberg back in through an iframe, which throws away the calm writing surface.

ECOSYSTEM
How complex blocks stay useful without parity

Islands preview with real front-end CSS. Schema-driven inspectors edit server-registered attributes. Generic text runs let you change copy inside static third-party markup without regenerating structure. Image swaps retarget mirrored URLs and media IDs. Design libraries and patterns insert as serialized markup that Gutenberg already considers valid. The deep link to the block editor remains for design-level work.

The Stackable lesson is the honest limit in one plugin.

I hit this wall while expanding Minn’s block support. Stackable is a popular design-oriented block suite. On the lab site, 46 of 47 of its blocks are static-save with effectively empty server attribute schemas. The remaining one is a hybrid: a render callback plus a JavaScript save() that emits wrapper HTML.

A bare self-closing comment for a hybrid block renders empty and fails Gutenberg validation. The server cannot see the plugin’s save(). So Minn’s auto-insert path runs a render probe and refuses candidates that render nothing. That is not a missing feature. That is refusing to write invalid posts.

The counter-move that works is data, not React reimplementation. Stackable publishes a design library as full serialized markup. Minn inserts free designs as islands. Text inside those islands is editable through generic text runs. Images can be swapped. Layout and spacing controls stay in Stackable’s own editor UI, one click away.

Chasing a plugin’s React design UI is a race you lose by winning
Every control you reimplement becomes a permanent parity treadmill against the vendor’s next release. Serialized markup inserts, schema forms for dynamic blocks, and an honest handoff scale. Rebuilding variation pickers and gradient panels inside a writing tool does not.

Could a lightweight framework reach full feature parity? No. For structural reasons.

I wanted a cleaner answer than “it would take a long time.” The blockers are structural.

  • Closed save() runtimes. Without executing each plugin’s editor JavaScript, you cannot regenerate static block HTML safely. Hosting that runtime is hosting Gutenberg.
  • Nested layout is a different product. Columns and covers need a hierarchical canvas, not a markdown-friendly contenteditable. A second page builder is still a page builder.
  • Validation is a two-sided contract. Gutenberg assumes the writer can re-save. Minn’s islands work because they refuse to re-save unknown blocks. Full parity requires the opposite policy.
  • Ecosystem gravity. Block plugins invest in SlotFill, inspector controls, and useBlockProps. Treating that UI as a deep link is correct. Treating it as a reimplementation target is how lightweight tools die.

You can get full parity with a lightweight framework only if you smuggle Gutenberg back in. At that point you no longer have a lightweight framework. You have two admins fighting in one window.

The size gap is the product gap, measured in megabytes.

What "lightweight" means in practice
Core editor package sum (min JS, typical post session)
~4.6 MB
block-library alone
~1.1 MB min
Minn Admin app.js (admin + editor + surfaces)
~1.3 MB unminified
Minn live-editable prose block set
16 names

Minn is not “Gutenberg with less CSS.” It is a different storage and edit contract. Prose is live DOM. Complex blocks are opaque documents with optional inspectors. The writing path leans on the browser’s own undo for typing and paste. Structural island deletions get an undo toast instead of a custom journal that would reimplement the whole stack.

The right north star is writing parity with layout coexistence.

Full feature parity with the block editor is the wrong goal for a calm writing tool. It forces you onto Gutenberg’s architecture, and then you have rebuilt the thing people open Classic to avoid.

What I care about is: every post on a real production site gets written and edited without opening the block editor once, while complex layouts remain safe and one click away when you actually need them.

That bar is realistic because of Classic’s install base, because Gutenberg markup is an open storage format, and because islands make the cost of not supporting a block small. The post still displays. The markup still survives. The block editor is still there for design work.

Gutenberg is the layout tool. Minn is where the writing happens.
Saying “that is Gutenberg’s job” is the strategy, not a limitation to apologize for. The hybrid model only works if the never-build list stays load-bearing: no columns as first-class canvas, no FSE, no parity treadmill. Grow the simple-block allowlist carefully. Prefer serialized design libraries over reimplemented React. Keep the handoff one click.

There is no free lunch where a vanilla contenteditable framework fully replaces React-block save() trees. There is a free lunch for the job most people still open Classic for: write, paste, focus, publish, and never corrupt the fancy bits.

Minn Admin is aimed at that free lunch. The official block editor is aimed at the universal one. Both can be correct at the same time, as long as we stop measuring a writing tool by whether it became a content OS.

If you want to try the writing surface, it is open source at minnadmin.com.