— Blog · notes

Why we took React and Babel out of our home page

Why a one-page studio site was shipping 4.2 MB of JavaScript, and what it ships now.

TL;DR · English

On 2026-09-05 we replaced the React + Babel-standalone setup behind oddduck.ooo with plain HTML. The old page downloaded 4,226 KB of JavaScript — Babel 3,064 KB, React DOM 1,055 KB, React 107 KB — and compiled its JSX in the browser before painting anything; on a wired line that alone took 512 ms. The new page ships about 11 KB of vanilla JS and paints in under 100 ms on a local server. Nothing visible changed.

The oddduck.ooo home page has been served as static HTML, without React, since 5 September 2026. Before that, it downloaded 4,226 KB of JavaScript and compiled JSX in the browser before the first pixel appeared. It is 11 KB now.

It started with one remark: "the site seems to take too long to load." We suspected the server first, but the server was fine. The problem was the way we had built the home page.

What was actually slow

fact Before the change, the home page's HTML body was effectively empty. The screen was drawn by React only after three scripts had arrived from an external CDN. The code that builds the screen was not compiled in advance, so every visitor's browser compiled it each time (as of 5 September 2026).

fact On 5 September 2026 we turned off the browser cache on a wired line and downloaded the three files again. This is the result.

FileSizeDownload
@babel/standalone 7.29.03,064 KB290 ms
react-dom 18.3.1 (development)1,055 KB146 ms
react 18.3.1 (development)107 KB76 ms
Total4,226 KB512 ms

Three things were stacked on top of each other. First, we were using development builds. With production builds the two React files drop from 1,162 KB to a little over 140 KB. Second, the 3 MB of Babel was purely the cost of compiling JSX in the browser. Third, the body was empty, so the first pixel was only drawn once all of that had arrived and finished compiling. Until then the visitor looked at a blank cream screen.

On a wired line the download alone is 0.5 seconds. On a line with a fifth of that bandwidth, simple arithmetic gives 2.5 seconds. On top of that, parsing a 3 MB script and compiling the JSX fall to the CPU of the visitor's device. The shape of the thing matched the complaint exactly.

For the record, the server was not the problem. fact Measured ourselves on the same day, our server answered the first byte in 100–270 ms, and all of the images together came to 270 KB.

Why we built it that way

This site started as a design prototype. To look at typefaces and copy while changing them, we put a tweaks panel in the corner of the screen. To get that done quickly we used React and compiled the JSX in the browser as well. A no-build rule meeting the urge to see something right away produces exactly this kind of structure. The prototype just went live as it was.

fact That tweaks panel was never once rendered for a visitor. Even so, its 25.8 KB of code was being compiled along with everything else on every visit.

What we replaced it with

The home page is a one-page introduction. The only thing that counts as state is whether the mobile menu is open. So instead of changing framework, we removed the framework.

  • We wrote the screen React used to build straight into the HTML. The browser draws it the moment it arrives.
  • The menu, the mobile drawer and the quack the duck makes when you press it moved into one small script.
  • The status strip and the keyboard shortcuts stayed in the small script that already held them.
  • Fonts are now fetched from the head of the document, instead of being pulled one after another from inside the stylesheet. Before, the fonts were fetched in order only after the stylesheet had fully arrived.
  • The tweaks panel and the files that needed compiling were deleted.

There is still no build step. We fix a file, upload it, and that is what is served. What we wanted to keep was "no build", not "React".

The result

fact After the change, on a local server (fonts cached): First Contentful Paint 96 ms, DOMContentLoaded 53 ms. Live, on a first visit, DOMContentLoaded 361 ms. The home page loads two scripts, about 11 KB together (measured 5 September 2026).

BeforeAfter
JavaScript4,226 KB + 49 KB of JSX11 KB
Compiling in the browserevery visitnone
To the first pixelafter everything arrives and compilesas soon as the HTML arrives
Build stepnonenone

Nothing visible changed. The colors, the type, the duck, the quack, the marquee, the mobile drawer are all the same. The only thing that changed is the route they take to reach the screen.

What we learned

Leave prototype tooling in the live site and the visitor pays for it. In particular, "no build" does not mean "no bundle". Compiling in the browser does not remove the build; it makes every visitor run one.

So our rules gained a line. Do not let React back in. If a reason to change the screen comes up we will think about it again, but no reason will come up to attach a 4 MB compiler to a single introduction page.