Time to first editor: how long eight visual editors take to appear
We built the same minimal editor eight times and measured it: Editor.js reaches a rendered editor in 70 ms and 5 lines of code, React Page takes 1,004 ms and needs a bundler alias to build at all against React 19.
TL;DR
- 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. Chromium (Playwright build 1194), headless, 5 loads per app, median reported.
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.








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: 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 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 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, 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 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: they load from vendor infrastructure behind licence keys, so there is no equivalent local build to time.
Reproducing this
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 --debugThe 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
- The benchmark applications, in full — accessed 2026-08-19
Written by the EditorStack Research Team. How we test and what we refuse to publish is on the methodology page.