我们为什么把主页上的 React 和 Babel 去掉了
Why a one-page studio site was shipping 4.2 MB of JavaScript, and what it ships now.
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.
从 2026 年 9 月 5 日起,oddduck.ooo 的主页不再用 React,直接以静态 HTML 提供。在那之前,第一个像素出现之前要先下载 4,226 KB 的 JavaScript,还要在浏览器里编译 JSX。现在是 11 KB。
起因是一句话:“打开网站好像太慢了。”我们先怀疑服务器,结果服务器没问题。问题出在我们做主页的方式上。
到底是什么慢
事实 改版之前,主页的 HTML 正文几乎是空的。画面要等三个脚本从外部 CDN 到齐,才由 React 画出来。生成画面的代码没有预先编译,所以每个访客的浏览器每次都得自己编译一遍(截至 2026 年 9 月 5 日)。
事实 2026 年 9 月 5 日,我们在有线网络上关掉浏览器缓存,把这三个文件重新下载了一遍。结果如下。
| 文件 | 大小 | 下载 |
|---|---|---|
| @babel/standalone 7.29.0 | 3,064 KB | 290 ms |
| react-dom 18.3.1 (development) | 1,055 KB | 146 ms |
| react 18.3.1 (development) | 107 KB | 76 ms |
| 合计 | 4,226 KB | 512 ms |
三件事叠在了一起。第一,我们用的是开发版构建。换成生产版,两个 React 文件会从 1,162 KB 降到 140 KB 出头。第二,3 MB 的 Babel 完全是为了在浏览器里编译 JSX 才付的代价。第三,正文是空的,所以要等这些全部到齐、编译完成,第一个像素才画得出来。在那之前,访客看到的是一片米色空白。
在有线网络上,光下载就要 0.5 秒。换成带宽只有五分之一的线路,简单算下来是 2.5 秒。再往上还要加解析 3 MB 脚本和编译 JSX 的时间,这部分由访客设备的 CPU 来扛。这个结构,和“太慢了”的感受正好对得上。
顺带一提,服务器不是问题。事实 同一天我们自己测,服务器的首字节响应是 100~270 ms,所有图片加起来也只有 270 KB。
当初为什么这么做
这个站点一开始是个设计原型。为了一边改字体和文案一边看效果,我们在画面角落挂了一个调节面板(tweaks panel)。想快点做出来,就用了 React,JSX 也干脆放在浏览器里编译。“不设构建步骤”的原则碰上“想马上看到”的心情,做出来的就是这样的结构。原型没改,直接上了线。
事实 那个调节面板从来没有对访客渲染过一次。可它那 25.8 KB 的代码,每次访问都跟着一起编译。
换成了什么
主页是个单页介绍站。能算作状态的,只有移动端菜单开没开。所以我们没换框架,而是把框架去掉了。
- 原来由 React 生成的画面,直接写进了 HTML。浏览器一收到就画。
- 菜单、移动端抽屉,还有点鸭子时的那声嘎,都挪进了一个小脚本。
- 状态栏和键盘快捷键留在原来那个小脚本里,没动。
- 字体改成在文档头部直接取,不再从样式表里一个接一个地拉。以前要等样式表全部到齐,字体才开始按顺序下载。
- 调节面板和那些需要编译的文件,删了。
构建工具还是没有。改完文件传上去,上线的就是它。我们想守住的是“不构建”,不是“React”。
结果
事实 改版之后,在本地服务器上(字体已缓存):First Contentful Paint 96 ms,DOMContentLoaded 53 ms。线上首次访问的 DOMContentLoaded 是 361 ms。主页一共加载两个脚本,合计约 11 KB(2026 年 9 月 5 日测量)。
| 之前 | 之后 | |
|---|---|---|
| JavaScript | 4,226 KB + 49 KB 的 JSX | 11 KB |
| 浏览器内编译 | 每次访问 | 无 |
| 到第一个像素 | 全部到齐并编译之后 | HTML 一到就画 |
| 构建步骤 | 无 | 无 |
看得见的东西一点没变。颜色、字体、鸭子、嘎声、跑马灯、移动端抽屉,全都一样。变的只是它们到达画面所走的那条路。
我们学到的
把原型阶段的做法留在线上,代价是访客来付。尤其要说清楚:“不构建”不等于“不打包”。在浏览器里编译并不是省掉了构建,而是让每一个访客各构建一次。
所以我们的原则里多了一行:不要再把 React 放回来。哪天画面有理由大改,我们会再想一想;但为了一个介绍页挂上 4 MB 的编译器,这样的理由大概不会有。