# Time to first editor: how long eight visual editors take to appear

*Source: https://www.editorstack.cc/research/time-to-first-editor-benchmark — last updated 2026-09-17. Licensed CC BY 4.0 with attribution to EditorStack.*

## Summary

- Measured on 20 August 2026 across eight minimal applications, median time from navigation start to a rendered editor ranges from 70 ms (Editor.js 2.31.6) to 1,004 ms (React Page 5.4.6) in headless Chromium on a local server.
- Lines of application code needed for a working editor range from 5 (Editor.js) to 21 (Craft.js), and the ordering is almost the inverse of how much editor UI each library gives you for free.
- React Page cannot be bundled against React 19 without an alias, because its react-dnd dependency imports react/jsx-runtime.js — a path React 19 no longer exports.

## The question this answers

Bundle size tells you what a library costs your users. It does not tell you what it costs you. This
benchmark measures the other half: how much code you write to get an editor working, and how long
that editor takes to appear.

Every application does the same job — mount the editor, register one custom block, load one piece
of initial content. No theming, no persistence, no plugins. They are short enough to read in full,
and they are in the repository so you can check that we did not quietly give one library an easier
task.

| Library | Lines of code | Direct packages | Time to editor (median) | Notes |
| --- | --- | --- | --- | --- |
| GrapesJS | 14 | 1 | 309 ms | — |
| Editor.js | 5 | 1 | 70 ms | — |
| Puck | 16 | 3 | 852 ms | — |
| Craft.js | 21 | 3 | 94 ms | — |
| Plate | 13 | 8 | 209 ms | — |
| Tiptap | 11 | 4 | 171 ms | — |
| Lexical | 25 | 4 | 159 ms | — |
| React Page | 12 | 6 | 1004 ms | Requires a bundler alias for react/jsx-runtime.js (react-dnd + React 19). |

*Run tte-2026-08-20, measured 2026-08-20.*

## What each one looks like

These are screenshots of our own builds, taken from the applications above at 1280×800. No vendor
marketing material appears on this page.

- [GrapesJS editor screenshot](https://www.editorstack.cc/screenshots/grapesjs-minimal.png) — our build, 14 lines of application code.
- [Editor.js editor screenshot](https://www.editorstack.cc/screenshots/editorjs-minimal.png) — our build, 5 lines of application code.
- [Puck editor screenshot](https://www.editorstack.cc/screenshots/puck-minimal.png) — our build, 16 lines of application code.
- [Craft.js editor screenshot](https://www.editorstack.cc/screenshots/craftjs-minimal.png) — our build, 21 lines of application code.
- [Plate editor screenshot](https://www.editorstack.cc/screenshots/plate-minimal.png) — our build, 13 lines of application code.
- [Tiptap editor screenshot](https://www.editorstack.cc/screenshots/tiptap-minimal.png) — our build, 11 lines of application code.
- [Lexical editor screenshot](https://www.editorstack.cc/screenshots/lexical-minimal.png) — our build, 25 lines of application code.
- [React Page editor screenshot](https://www.editorstack.cc/screenshots/react-page-minimal.png) — our build, 12 lines of application code.

The screenshots are the most useful part of this benchmark, because they show the thing the numbers
cannot: what "working out of the box" actually means for each library. Editor.js and Plate render a
bare editing surface, because that is what they are. GrapesJS and Puck render a full editor chrome
with panels, because that is what they are. Craft.js renders your component and nothing else,
because building the chrome is your job.

## Reading the results

**Fewer lines does not mean faster to ship.** Editor.js needs five lines and 70 ms, and it is the
fastest here on both counts — but it is a block-based document editor, not a page builder, so for
a landing-page use case the comparison is not a fair fight. Craft.js needs 21 lines because you
write the components yourself, and those 21 lines are the beginning of a much longer project than
GrapesJS's 14.

**The React libraries pay a boot cost.** Puck at 852 ms and React Page at 1,004 ms are the two
slowest, and both boot React, hydrate an editor shell and render a preview surface before the
editor exists. Craft.js is the exception at 94 ms precisely because it renders almost nothing.

**GrapesJS sits in the middle at 309 ms** and does the most work of the framework-agnostic options:
it creates an iframe canvas, loads its stylesheet, builds the panels and initialises seven managers
before the editor element appears.

**One library will not build at all without help.** `@react-page/editor` pulls in `react-dnd`,
which imports `react/jsx-runtime.js`. React 19 does not export that path, so the bundle fails
outright until you alias it. We recorded the workaround rather than hiding it, because a build-time
alias in your bundler config is a real integration cost that no feature table mentions.

## Method

Each application is installed in an isolated directory with its own dependency tree, bundled with
esbuild in production mode, and served from a local HTTP server. The page is loaded five times in
headless Chromium; the median is reported. Timing is taken inside the page with `performance.now()`
at the moment the editor's root element is first present.

The entry file is copied into the application directory before bundling. That detail matters: our
first attempt built the entry from this repository's own tree, which resolved React from here while
each editor resolved React from its own install. Two React copies produce null hook internals and
three libraries appeared to be broken. They were not. If you take one practical lesson from this
page, take that one — a "library incompatible with React 19" bug report is a duplicate React until
proven otherwise.

Lines of code are counted excluding blank lines and comments, on the entry file only. Imports count,
because you have to write them.

## Limitations

These timings come from a machine with no CPU throttling, no network latency and a warm cache. Real
users have all three. Treat the ordering as meaningful and the absolute values as a floor.

The definition of "ready" favours libraries that render a minimal surface. An editor whose root
element exists but whose panels are still populating will look faster here than it feels. We chose a
definition we could apply identically to eight very different libraries rather than one that flatters
any of them.

Only open-source libraries are included, for the same reason as the
[bundle size benchmark](/research/bundle-size-benchmark-2026): commercial SDKs load from vendor
infrastructure behind a licence key, so there is no equivalent local build to time.

## How to cite this

> EditorStack (2026). *Time to first editor benchmark*. Measured 19 August 2026.
> https://www.editorstack.cc/research/time-to-first-editor-benchmark

Raw results are in `data/measurements/time-to-editor.json` and the applications are in
`benchmarks/time-to-editor/`, both under CC BY 4.0. See [how to cite](/cite) for BibTeX.

## Per-application notes

**Editor.js — 5 lines, 70 ms.** One import, one constructor call, one piece of initial data. It is
the shortest integration in the dataset because the library's scope is the narrowest: a linear stack
of blocks with no layout and no styling. The number is real, and reading it as "easiest page builder"
is the mistake it invites.

**Craft.js — 21 lines, 94 ms.** The most code and nearly the least time, which captures the library
exactly. Most of those 21 lines define two components and attach connectors; almost none of them
configure an editor, because there is no editor to configure. What renders is a draggable heading.

**Plate — 13 lines, 209 ms.** A bare editing surface with a placeholder. A realistic Plate editor adds
a dozen plugins and a toolbar, and both numbers rise. Note that npm resolved 48.0.5 while the registry's
latest tag pointed at 49.0.0 — we report what was measured.

**GrapesJS — 14 lines, 309 ms.** The 14 lines produce a complete editor with panels, a style manager
and a device switcher; the 309 ms is what building all of that costs. Of the eight, this is the largest
gap between what the code says and what appears on screen.

**Puck — 16 lines, 852 ms.** Boots React, renders an editor shell and an iframe preview. The slowest
but one, and the second-most complete result. On a dedicated editor route this is a one-time cost
users open deliberately.

**React Page — 12 lines, 1,004 ms.** The fewest lines of the React options and the slowest to render,
because it ships a Material UI editor interface. It is also the only application that required a
bundler workaround to build at all.

## The mistake we made, and why it is on this page

Our first run reported that Puck, Craft.js and Plate all failed against React 19 with null hook
internals, and that React Page failed to build. Three simultaneous library incompatibilities would
have been the most eye-catching finding on this site.

It was wrong. The entry files were being bundled from this repository's own tree, which resolved
`react` from our `node_modules` while each editor resolved it from its own install. Two React copies
in one bundle produce exactly that symptom.

We fixed the build, re-ran, and all eight libraries measured successfully. The incorrect finding never
appeared on the site, and it is recorded here and in the [changelog](/changelog) because a benchmark
that only publishes its successes is not evidence.

The practical lesson generalises: a "library incompatible with React 19" report is a duplicate React
until proven otherwise. Check `npm ls react` before opening the issue.

## Four applications built but not yet in the table

On 17 September 2026 we added applications for [CKEditor 5](/libraries/ckeditor), TinyMCE, BlockNote
and Quill to `benchmarks/time-to-editor/`. They build and render, and their screenshots and line counts
appear on their reviews. Their timings are not in the table above.

The reason is the machine. Those runs were taken on a different computer from the published run, and
we re-timed the eight published applications there in the same session to check whether the results
could be combined. They could not: the published applications ran between 1.1 and 3.7 times faster
than their published medians, and not by a constant factor, so no single correction would make the
two sets comparable. The calibration data is kept in `data/measurements/` rather than shown here. The
four join the table when all applications are re-run on one machine.

## What this benchmark does not tell you

**Field performance.** Warm local server, no network latency, no CPU throttling. Real users have all
three, and mid-range mobile hardware would change these numbers substantially — though the ordering
would likely survive.

**Editor readiness as a user perceives it.** Our definition is the moment the editor's root element
exists. An editor whose panels are still populating counts as ready here and does not feel ready to a
person. We chose a definition applicable identically to eight very different libraries over one that
matched perception for any single library.

**Time to a shipped product.** This is the measurement people most want and the one no benchmark can
provide. Our [build-versus-buy calculator](/use-cases/build-vs-buy-visual-editor) is the honest
substitute: roughly 520 hours for a customer-facing editor, of which integration — the part this page
measures — is about 40.

**Commercial SDKs**, for the same reason as the [bundle benchmark](/research/bundle-size-benchmark-2026):
they load from vendor infrastructure behind licence keys, so there is no equivalent local build to
time.

## Reproducing this

```bash
git clone https://github.com/GoodPHP/gjs-comparison
cd gjs-comparison
node scripts/measure-time-to-editor.mjs              # every application, one machine
node scripts/measure-time-to-editor.mjs --only quill --merge --reference editorjs
                                                     # re-time one app, keep the rest, record calibration
node scripts/measure-time-to-editor.mjs --only puck --debug
```

The applications are in `benchmarks/time-to-editor/`, one directory per library, each short enough to
read in a minute. If you think one of them gives a library an unfair advantage, the file is right
there and we would rather hear about it than not.


## Frequently asked questions

### What counts as the editor being ready?

The moment the editor's own root element exists in the DOM — .gjs-editor for GrapesJS, .codex-editor__redactor for Editor.js, [data-slate-editor] for Plate, and so on. It is measured with performance.now() inside the page, so it excludes browser startup and includes everything the library does after the script runs.

### Are these numbers representative of production?

No, and they are not meant to be. They come from a warm local server with no network latency and no CPU throttling. Their value is relative: they tell you which libraries do more work before showing an editor, not what your users will experience.

### Why does Craft.js need the most lines of code?

Because it supplies the least. Craft.js gives you the node tree and drag-and-drop behaviour and expects you to write the components, including the connectors that make each one draggable. The other libraries hand you more, so the entry file is shorter.


## Sources

1. [The benchmark applications, in full](https://github.com/GoodPHP/gjs-comparison/tree/main/benchmarks/time-to-editor) — accessed 2026-08-19
