ChatGPT 网页版逆向工程全解析:十亿级用户背后的前端架构与性能哲学
前言
深入拆解 chatgpt.com 的页面源码、打包产物与网络请求,揭示 OpenAI 如何用主流技术栈打造出全球访问量最大的 AI 应用 —— 从框架迁移、流式渲染到无登录体验的每一个工程抉择。
今日前端早读课文章由 @Dennis Brotzky 分享,@飘飘编译。
用于解决实际工作中的 C 端产品首屏加载慢 / 弱网体验差 问题,最值得借鉴的一点是:指标聚焦与渐进渐入。应重新梳理核心用户旅程,找出产品唯一的 “灵魂输入框(或核心动作)”,以此动作的可交互时间(TTFI)为最高优先级重构代码分割图谱,将非必要组件与第三方 shake 掉或延后下载。
译文从这开始~~
打开一个新标签页,输入 chatgpt.com,随便问它点什么。无需注册,无需登录,没有加载转圈。页面瞬间可交互,你按下回车后,答案便如流水般逐字涌出。这套体验面向的是大约十亿用户 ——chatgpt.com 已跻身全球访问量最高的网站之列,也是世界上使用最广泛的 Web 应用之一。表面上看,它的界面简洁至极,但深入探究之后你会发现,它远非表面那般简单。我花了好几天时间,通过翻阅页面源码、打包后的代码和网络请求,对它进行逆向工程,只为弄清楚它究竟是怎么构建的。
本文将深入探讨的内容
- 从约束条件说起
- 从 Next.js 迁移到 React Router
- 最快的首屏渲染路径
- 十亿用户规模下的 CSS 方案
- 组件层面不必重复造轮子
- 每一次回答都是一道渲染难题
- 输入框能打字了吗?
- 一切皆可用功能开关控制
- 通往首个 token 的最短路径
- 向十亿陌生人敞开大门
- ChatGPT 与 Claude 的差异
在正式开始之前,有几点需要说明:我并非 OpenAI 的员工,也从未接触过他们的源代码。这篇文章与我之前拆解 Linear 和 Conductor 的系列文章不同 ——OpenAI 几乎没有公开发表过任何关于 ChatGPT 网页版技术实现的内容。因此,本文是历史考据、技术调查与工程洞察的结合体。所有内容均来源于 chatgpt.com 本身(包括 HTML、JavaScript、CSS 和网络请求),以及零散的技术演讲、推文和他们的招聘页面。
从约束条件说起
在设计一个 Web 应用时,我最看重两件事:目标与约束。这两个因素决定了后续的一切技术选型。用户是谁?首屏加载有多重要?是否需要登录才能使用?需要被搜索引擎收录吗?预算多少?开发者体验有多重要?是否存在既有架构?诸如此类……
从外部视角来看,ChatGPT 的目标在我看来非常清晰:成为全世界与 AI 对话的入口。该网站必须确保全球任何用户、在任何设备上都能轻松访问,这一点至关重要。它应当服务于所有用户,无论是网络信号不稳定、刚接触 ChatGPT 的安卓手机用户,还是使用 MacBook Pro 并拥有 Pro 订阅的高级用户。
一旦明确了这些约束,许多架构方案就直接被排除了。标准的客户端渲染(CSR)行不通 —— 用户得盯着一片空白,等一大堆 JavaScript 下载完毕后才能看到任何有意义的内容。Linear 这样的产品可以采用 CSR,因为它的用户登录一次后就整天泡在应用里,但这种方式并不符合 OpenAI 的目标。
在这种场景下,服务端渲染(SSR)是更合适的选择,但它也有代价。每一个请求都需要服务器去拉取数据、渲染 HTML、然后返回给客户端,运行成本远高于直接分发静态资源。对开发者而言,这也是一个更难驾驭的模型 —— 你的应用同时运行在两个环境中(服务端和浏览器),你必须时刻清楚哪些 API 在哪个环境可用。你迟早会撞上 hydration 不匹配的问题,不小心在服务端引用了 window,或者花大量时间调试缓存和渲染行为。这是一套更复杂的架构,故障模式也更多。但如果你的目标是最快的首屏加载、为搜索引擎提供真实 HTML、以及为首次访问的用户带来极致体验,那么这些取舍往往是值得的。
来看看 ChatGPT 是怎么做的。第一步是掀开引擎盖,深入了解他们的架构与技术决策。据我所能梳理的信息,以下大致是截至目前的技术栈:
前端
React 19 + react-dom (UI 运行时,流式服务端渲染)
React Router 7(框架模式) (路由、loader、流式 SSR)
TypeScript (开发语言)
TanStack Query (客户端的服务端状态管理)
Tailwind CSS (原子化样式 + 设计令牌)
Radix UI 基础组件 (菜单、弹出层、选择器、Toast)
ProseMirror (用户输入的编辑器)
CodeMirror 6 (回答中的代码块 + Canvas 编辑器)
Motion (动画)
silk-hq (移动端原生体感的底部弹出面板)
KaTeX (数学公式;字体按需加载)
Mapbox GL (回答中的地图展示)
系统字体栈 (UI 文本不加载 Web 字体)
构建与分发
Vite (打包工具;按路由拆分 JS + CSS 块)
Cloudflare (CDN、缓存、Bot 防御)
第一方资源托管 (所有资源均来自 chatgpt.com/cdn/assets)
实验与可观测性
Statsig (功能开关 + A/B 实验,服务端求值)
Datadog RUM (真实用户监控)
实时通信
基于 fetch 的 SSE (token 流式传输)
LiveKit + WebRTC (语音模式)
WebAssembly (音频处理、语法解析)
最让我印象深刻的是,他们大量采用了现成的开源库,而非自研框架或组件。如果你看过 Gemini 的源码,会发现那完全是另一个世界 —— 充斥着 Google 的私有技术栈。相比之下,ChatGPT 给人的感觉是简洁且标准,而当你要构建一款面向十亿用户的应用时,这恰恰是极为重要的品质。
从 Next.js 到 React Router 的迁移
ChatGPT 的技术演进史非常有意思。在深入剖析它当前的技术实现之前,我想先梳理一下它的起源、关键节点,以及走到今天的路径。
ChatGPT 于 2022 年 11 月 30 日上线,最初是一个基于 Next.js 12 的应用,使用的是 Pages Router(至今仍是我个人最喜欢的 Next.js 版本)。在 OpenAI 内部,ChatGPT 只是一个 “研究预览版”,期望值很低 —— 只是对微调后的 GPT-3.5 做了一个快速封装,目的是收集公众反馈。我们都知道后来的结果如何了。
Next.js 时代留下了一个我特别喜欢的历史遗迹 —— 路由清单,至今还静静躺在 Wayback Machine(互联网档案馆)里。从中可以看出最初的 Web 应用有多简单,还能瞥见一些内部的 codespace 和 workspace 工具,它们似乎被打包在同一个构建产物中 —— 大概率是为了赶 MVP 进度而留下的痕迹。
// 2022 年 12 月上线版本的 _buildManifest.js
sortedPages: [
"/",
"/_app",
"/_error",
"/auth/error",
"/auth/login",
"/chat",
"/codespace",
"/error",
"/workspace/[[...tid]]"
]
在随后的整个 Next.js App Router 时代 ,chatgpt.com 自始至终都没有采用它。他们在 Pages Router 上坚守了大约 21 个月,然后彻底告别了 Next.js。在我过去的项目中,我也走过同样的路。
这次迁移并非由 ChatGPT 官方宣布,而是被人在野外抓了个现行。2024 年 8 月 27 日,Tibor Blaho 发现 chatgpt.com 正在向一部分用户灰度推送一个 Remix 构建版本。到 9 月 4 日,新版本已大范围铺开,Remix 的联合创始人 Ryan Florence 发推说:“又一个 Remix 应用上线了:chatgpt.com。” 第二天,Wes Bos 就在 YouTube 上拆解了它的打包产物。
Wes 的发现才是最有趣的部分,因为它揭示了背后的设计哲学。Remix 时代的应用确实跑着真正的服务端基础设施(一个 Express 服务器执行路由 loader,并将大约 7000 行 JSON 序列化到 window.__remixContext 中),但在服务端几乎不渲染任何实际 UI—— 只有一个空壳、一些预加载链接、一段主题脚本。整个应用里找不到任何 Remix action,所有数据变更都走自己的 API,而非 Remix 内置的 action 机制。Remix 在这里充当的角色,不过是一个路由器、一条数据管道、一个 hydration 的外壳 —— 包裹着的本质上还是一个客户端应用。Ryan Florence 后来也证实,ChatGPT 是一个 Remix 应用,但 "它们的大部分数据加载是通过 TanStack Query 而非 React Router 的 loader 完成的。"Vite 的作者尤雨溪用一句话精辟概括了社区对这次迁移的解读:“你们很多人大概也只需要一个 SPA 就够了。”
所以到目前为止,故事线是这样的:先用 Next.js Pages Router 做了最初的 MVP,然后迁移到了以类 SPA 方式运行的 Remix。但故事还没完!
当 Remix v2 于 2024 年 11 月合并回 React Router v7 时,ChatGPT 也跟着迁了过去。如今你查看任意页面的源码,都能看到:
window.__reactRouterContext = {
"basename": "/",
"ssr": true,
"isSpaMode": false,
"routeDiscovery": {
"mode": "lazy",
"manifestPath": "/__manifest"
},
// ...
};
ssr: true。这是 React Router 7 的框架模式 —— 基于 Vite 的完整流式服务端渲染方案。而这正是我觉得最引人入胜的演进过程:Remix 时代只返回一个空壳,但今天的应用已经将整个未登录体验以真实 HTML 的形式在服务端完整渲染。他们不仅没有放弃 SSR,反而投入得更深了。
还有一个发现的独特之处隐藏在路由中。客户端清单中列出了 354 条路由,其中只有约 13 条属于聊天应用本身,其余全是营销和落地页面 —— 全部共存于同一个代码库、同一个路由器、同一次部署中。路由命名完全遵循 React Router 的文件约定:
routes/_conversation._index 聊天主界面
routes/_conversation.c.$conversationId 单个对话
routes/($lang).codex.pricing 营销页
routes/($lang).business._index 营销页
routes/($lang).atlas.get-started 营销页
大多数公司倾向于将营销站点独立部署(比如 Linear 就把营销站放在单独的 Next.js 项目里,与产品应用分开)。但据我观察,OpenAI 将营销页面直接放在了产品应用内部,这意味着落地页与产品共享同一套设计系统、同一套功能开关体系、同一个路由器。从推广页面点击进入聊天界面,走的是客户端导航,而非一次全新的页面加载 —— 这一点确实很酷。
chatgpt.com 页面源码,展示了 reactRouterContext 中 ssr 设置为 true
最快的首屏渲染路径
我测量到的未登录页面文档压缩后仅有 84 KB,从温哥华的笔记本电脑发出请求,由就近的 Cloudflare 边缘节点响应,首字节时间(TTFB)大约在 50 到 65 毫秒之间。这一个响应中包含了渲染可用应用外壳所需的一切:大约 30 KB 的真实标记(侧边栏、“今天有什么安排?” 的问候语、输入框)加上渲染所需的样式。Hydration 完成后,整个页面仅有 548 个 DOM 节点(非常轻量)。整个体验完全聚焦于简洁,只有一个目标:那个聊天输入框。
但标记和 DOM 其实是这份文档中最不有趣的部分。真正的性能功夫全在 <head> 里。在任何打包文件被请求之前,内联脚本已经开始执行了。第一个是标准的主题选择脚本:
// 内联在 <head> 中,在首次绘制之前运行
!function(){try{
var d=document.documentElement, c=d.classList;
c.remove('light','dark');
var e=localStorage.getItem('theme');
if('system'===e||(!e&&true)){
var t='(prefers-color-scheme: dark)', m=window.matchMedia(t);
if(m.media!==t||m.matches){ d.style.colorScheme='dark'; c.add('dark') }
else { d.style.colorScheme='light'; c.add('light') }
} else if(e){ c.add(e||'') }
if(e==='light'||e==='dark') d.style.colorScheme=e
}catch(e){}}();
是不是很眼熟?这正是我在 Linear 那篇文章中介绍过的模式:在绘制之前从 localStorage 读取用户保存的主题偏好,并将其应用到 html 元素上,从而彻底避免主题闪烁。
紧接着出现的是我最喜欢的一个细节,它透露出 ChatGPT 对性能的执念:
window.__oai_logHTML?window.__oai_logHTML()
:window.__oai_SSR_HTML=window.__oai_SSR_HTML||Date.now();
requestAnimationFrame(function(){
window.__oai_logTTI?window.__oai_logTTI()
:window.__oai_SSR_TTI=window.__oai_SSR_TTI||Date.now()
});
他们在 HTML 开始执行的那一刻和紧随其后的第一帧分别打上时间戳,对每一个用户都是如此,然后将数据喂入真实用户监控系统。性能显然是重中之重:他们在度量整条加载链路,从首字节到可交互。你无法优化你没有度量的东西!
文档响应本身是流式传输的。这是 React 19 通过 React Router 实现的流式 SSR:服务器立即刷出外壳,Suspense 边界随着数据解析逐步填充,由微小的内联脚本就地拼接。路由 loader 数据通过独立的 ReadableStream 与 HTML 并行到达。而在 loader 数据中,还藏着一组服务端对客户端预热行为的决策:shouldPrefetchModels、shouldPrefetchHistory、shouldPrefetchStarredConversations。在 chatgpt.com 的架构中,服务端主导着客户端的大量行为 —— 它为客户端下发一份按用户定制的预取计划。给人的感觉是,客户端只是一层薄薄的壳,一切由服务端驱动。
仔细想想,对于这个场景而言,这是一种非常出色的方案。如果你相信生成式 UI 的未来,这就是通往那个方向的第一小步。而且,聊天界面本身就是同样的思路 —— 回答通过 token、Markdown、HTML 和其他控件信号来决定该展示什么。因此,整个 HTML 文档就是这种理念的一面镜子。
身份认证采用的是先渲染、后验证的策略,我反复强调过,如果性能是你的目标,这就是最佳方案。未登录的访客永远不会被身份验证检查阻塞。相反,整个后端并行提供了一套匿名接口:/backend-anon/me、/backend-anon/models、/backend-anon/conversation。匿名访客会获得一个真实的用户 ID,这样速率限制和实验系统无需账户也能正常运作。在新用户和输入框之间,不存在任何 “你登录了吗” 的往返请求 —— 这正呼应了我们一开始讨论的那些约束条件。
以上所有设计都指向同一个目的:让尽可能多的人在浏览器中输入 chatgpt.com,然后立刻开始使用 AI。服务端渲染的文档带来极致的可交互时间。在此基础上,没有登录墙,也无需创建账户 —— 直接消灭了漏斗中最大的流失环节。
chatgpt.com 在禁用 JavaScript 的情况下加载服务端渲染外壳的截图
十亿用户规模下的 CSS 方案
ChatGPT 是全球最大的 Tailwind 应用之一。Adam Wathan 早在 2023 年 3 月就将它加入了 Tailwind 的展示案例。2024 年 OpenAI 换掉了整个框架,但 Tailwind 却毫发无损地存活了下来。Wes Bos 在 Syntax 节目中跟 Tanner Linsley 开玩笑说:“在那次大换血中,只有你(TanStack Query)和 Tailwind 没被砍。”
Tailwind 的一大优势是,相比 styled-components 等 CSS-in-JS 方案,它的 SSR 实现要简单得多。更不用说,直接通过样式表而非脚本来提供 CSS,省去了所有额外开销。
深入看看,随便审查一个元素,都能看到标志性的 Tailwind 类名,但有一个独特之处:
<div class="bg-token-main-surface-primary border-token-border-light
flex h-svh w-screen flex-col @container/thread">
那些 token-* 类是设计系统的一部分:一层语义化的 CSS 变量层,铺设在工具类之下。浅色模式、深色模式以及可自定义的聊天主题,全部通过切换变量值来实现,而无需重新渲染组件。这是 Tailwind 相对于 styled-components 等 CSS-in-JS 工具的又一个优势。
<head> 中的第二个内联脚本会从 localStorage 读取你保存的聊天主题,并在绘制之前在 html 元素上设置一个 data 属性 —— 和暗色模式脚本如出一辙。
CSS 按路由级别分片交付,与 JavaScript 的拆分策略一致。有一个较大的根样式表和一个更大的对话样式表,然后更加细粒度:有 code-block.css、cot-message.css、global-modals.css,每一个都跟随对应的功能按需懒加载。他们在根据组件或路由来拆分样式方面做得非常出色。这样一来,加载首屏时你只需要等待最核心的内容。
chatgpt.com 加载的各个 CSS 文件
我最欣赏的 CSS / 设计决策,恰恰是一个他们没有做的决定:不使用 Web 字体。UI 文本直接使用平台自带的字体栈渲染(Apple 设备上是 SF Pro,Windows 上是 Segoe UI,Android 上是 Roboto)。整个页面唯一请求的字体文件是一个极小的半粗体 woff2,仅用于品牌展示场景,而 KaTeX 数学字体只有在回答中包含数学公式时才会下载。当你的受众涵盖数亿网络条件不佳的用户时,最快的字体就是设备上已经装好的那个。
font-family: -apple-system-body, ui-sans-serif, -apple-system, "system-ui",
"Segoe UI", Helvetica, "Apple Color Emoji", Arial, "sans-serif",
"Segoe UI Emoji", "Segoe UI Symbol"
除此之外,我认为 CSS 方面最大的亮点是:高度标准化。他们确实为各种配色方案和基础变量设置了大量的主题变量,但总体而言,他们依赖的是标准的 Tailwind 模式和代码拆分。简洁、高效、高性能。
组件层面不必重复造轮子
打开 DOM 审查,到处都是 data-radix 属性:菜单、选择器、Toast、滚动区域、弹出层。ChatGPT 使用的无障碍基础组件,和每个人的业余项目用的一模一样 —— 而这正是关键所在。Radix 同样也是 Linear 和 Conductor 使用的方案,在我拆解过的所有应用中取得了三战三胜的战绩。
Radix 下拉菜单作为 ChatGPT 下拉组件的基础原语
输入框是他们真正投入复杂度的地方。看起来像个简单的 textarea,实际上是一个完整的 ProseMirror 编辑器。服务端渲染出一个视觉上完全一致的静态占位元素,等打包文件到达后,ProseMirror 在其之上完成 hydration。OpenAI 对这一层的重视程度可见一斑 —— 他们赞助了 Marijn Haverbeke,此人同时是 ProseMirror 和 CodeMirror 的作者,而他的 CodeMirror 也正驱动着 Canvas 中的代码编辑功能。
ProseMirror 作为 ChatGPT 输入框的富文本编辑器
如果你曾参与过富文本编辑器的开发,一定深知排版、光标定位、内嵌控件、@提及等功能的痛苦。ProseMirror(以及基于它构建的 TipTap 等库)能极大地减轻你的负担。
总体来看,我发现整个设计系统非常克制。几乎没有动画,下拉菜单瞬间弹出,配色方案极其简洁,形状也毫不复杂。这又回到了对约束条件的深刻理解 —— 如果你要构建一个面向所有人的应用,就必须让 UI 尽可能简单。说实话,我觉得这种极简之美令人赞叹。一切都是功能优先于形式。

在性能方面,有一个发现让我颇感意外:消息列表没有做虚拟化。对话中的每一条消息都留在 DOM 中。据我判断,ChatGPT 的大多数对话都比较短,而页内搜索功能需要对十亿用户正常工作,虚拟化则会带来实实在在的复杂性和无障碍性代价。再一次回顾最初的约束条件,很明显他们为更广泛的受众做了优化。我也确信他们手握大量关于平均聊天历史长度的数据来支撑这一决策。
关于数据获取的一点题外话
客户端数据获取跑在 TanStack Query 上,而且这并非最近才引入的。当 Remix 迁移引发热议时,Tanner Linsley 在 Syntax 节目中透露,“他们已经用了很长一段时间了”,并在整个框架切换过程中一直保留着它。这个集成远不只是在根节点放一个 Provider 那么简单。服务端返回的文档会初始化一个 window.__REACT_QUERY_CACHE__ 全局变量,用于承接服务端数据的 hydration,而紧挨着它的还有一个三行的 Promise.withResolvers polyfill。
我个人非常喜欢 TanStack Query 以及 Vercel 出品的 SWR 等类似库。它已经成为配置客户端数据获取最简便的方式 —— 涵盖乐观更新、服务端渲染和缓存管理。在这里,他们充分利用了它的全部能力来做服务端渲染,效果非常好。
每一次回答都是一道渲染难题
输入框是你输入的地方,但回答才是 ChatGPT 真正的生命所在 —— 而渲染一条回答,比看起来要难得多。如果你曾经构建过流式聊天界面,就深知其中的艰辛。打开网络面板,回答是一连串 token 组成的流。同时打开 DOM 审查,你会看到一份文档随着每一个 token 的到来被不断拆解又重建。分块处理、滚动同步、重复渲染等等边界情况层出不穷,让这件事异常棘手。
每一个到达的 token 都是一段 Markdown 片段,应用在原地对不断增长的消息进行重新解析和重新渲染。问题在于,流式传输过程中 Markdown 几乎总是不完整的:一个还没闭合的代码围栏、一张只有三行的表格、一个还在等待配对的加粗标记。如果解析不当,解析器每一帧都在改变自己对内容的判断,布局就会逐帧崩坏。但 ChatGPT 在这一切面前始终保持流畅。
有一个细节让我尤其喜欢:代码块实际上是什么。它看起来像一个带样式的 <pre>,但实际上是一个完整的 CodeMirror 编辑器(出自 ProseMirror 同一位作者之手),和驱动 Canvas 功能的是同一个库。审查回答中的任意代码块,你都能在 DOM 中找到 .cm-editor 和 .cm-content,语法高亮和复制按钮一应俱全。

数学公式同样受到精心对待。公式通过 KaTeX 渲染,而且每一个公式都输出了两份。一份是你看到的视觉版本,另一份是隐藏在其下方的 MathML <math> 语法树。第二份才是真正体现用心之处 —— 它让屏幕阅读器能够把公式朗读出来,也让你可以选中并复制真正的数学表达式,而非一张数学公式的截图。这是一个很容易被忽略的细节 —— 在大多数应用会直接渲染成一张扁平图片的地方,他们没有偷这个懒。

这与整个应用的一贯风格完全吻合。回答周围的一切都刻意做得平淡无奇、大众化、可复用。唯独回答本身,才是他们真正倾注工程心血的地方 —— 因为对于一个全部价值都在于响应内容的产品而言,响应的渲染就是产品本身。
输入框能打字了吗?
冷启动、未登录状态下加载 chatgpt.com,会拉取超过一百个 JavaScript 分块。文档中通过 modulepreload 预加载的只有 14 个关键文件(入口文件、React 供应商分块,以及匹配路由所需的模块),其余一切都被排到唯一重要的那个问题之后:你现在能输入了吗?
我之所以知道这就是核心问题,因为我在代码里就找到了答案。在服务端的启动配置中,有一个标志位赫然叫做 deferStartupImportsUntilComposerTTFI—— 推迟启动导入,直到输入框可交互(composer 就是聊天输入框)。整个启动序列都围绕着你能开始打字的那一刻来编排。
这是又一个例证:当你从一开始就彻底理解了目标,工程决策自然水到渠成。一切都聚焦在让用户能够在 ChatGPT 中打字这件事上。正如我们此前所见,可交互时间是他们重点监控的关键指标。这个指标存在的全部意义,就是确保当有人访问 chatgpt.com 时,输入框的打字操作不被阻塞。
路由分块还讲述了另一个故事。大多数文件名都是匿名的哈希值,但对话界面本身的分块却有一个具名标识:conversation-small。这是聊天功能精简后的核心模块,在路由清单中被引用了数百次,而代码块渲染、思维链展示等组件则各自独立成块,只在回答需要它们时才加载。新访客下载到的聊天版本,是刻意精简过的那一个,其余一切按需加载。这是一种精妙的技术手段,确保只在需要时才加载相应模块。
分块依赖图中还有一个细节值得一提:几乎所有资源都是第一方的。每一个分块、样式表和字体都来自 chatgpt.com/cdn/assets,经由 Cloudflare 分发并带有 30 天的缓存头。没有第三方 CDN 域名,意味着没有额外的 DNS 查找,也没有额外的 TLS 握手。切记:网络是敌人,每一个新的源站都是另一个延迟来源。
我经常看到团队在域名头部配置或域名设置上犯错,导致产生一次握手请求,直接让网络请求时间翻倍。这明明是很简单的修复,但我总是惊讶于有多少工程团队在这上面栽跟头。务必避免 CORS 问题、多余的 OPTIONS 预检请求和不必要的请求握手。
最后,还有一个值得注意的 “缺失”:没有 Service Worker。Linear 会预缓存大约 1200 个资源,让应用能够离线启动。ChatGPT 则除了标准的 HTTP 缓存之外什么都不缓存。在我看来,这是刻意为之。他们持续不断地部署更新,一个离线的聊天应用在模型端不在线时毫无用处,而一个过期的 Service Worker 是极少数能够 “活得比你为它部署的修复还久” 的 bug 之一。当你的产品本质上就是一场网络对话时,离线优先给你带来的不是价值,而是复杂性。
用功能开关控制一切
在服务端返回的 HTML 中,埋着一个 script 标签,承载着 377 KB 的 JSON 数据:客户端引导配置。其中包含你的认证状态、语言区域、地理位置,以及一份服务端计算的完整实验状态快照 —— 根据每次请求的匿名 ID 和地区实时生成。在我检查的那天,这意味着 556 个功能开关、144 个动态配置和 192 个实验层,全部内联到每位访客的文档中:
"statsigPayload": {
"feature_gates": {
"3479398748": {
"name": "3479398748",
"rule_id": "6cYbYFM2vjVEPNwAxdQvEB:100.00:3",
"value": true
}
// …… 大约还有 ~555 个
},
"dynamic_configs": { },
"layer_configs": { }
}
其中有三个决策值得深入探讨。第一,功能开关在服务端计算并内联到文档中。 常规的集成方式是在启动时从开关服务拉取配置,这就迫使你在两难之间抉择 —— 要么阻塞渲染等待网络响应,要么先渲染默认值、等开关到位后再闪烁切换。OpenAI 直接把这个请求彻底消灭了。等 React 开始 hydration 时,每一个开关的结果都已经有了答案。最好的网络请求,就是你根本不需要发出的那一个。
第二,开关名称经过了哈希处理。 你看到的是 3479398748,而非可读的名称。有一整个 “泄密生态” 在挖掘 ChatGPT 前端代码中尚未发布的功能,而可读的开关名称等于直接把产品路线图拱手奉上(写这篇文章时我自己也干过这事)。
第三,实验流量走的是第一方通道 事件日志通过他们自己域名下的 chatgpt.com/ces/v1/ 上报,而非 Statsig 的端点(Statsig 历史上会轮换使用 featuregates.org 之类的域名来规避广告拦截器)。把流量路由到自己的源站意味着,驱动每一个决策的实验数据不会出现一块形如 “所有使用广告拦截器的人” 的盲区。
但我最喜欢的开关,是那些直接指向加载策略本身的。在 deferStartupImportsUntilComposerTTFI 旁边,还有 promoteCss 和 stripModulepreloadImports—— 这些服务端开关的名字直白地说明了它们的作用:改变文档加载自身 CSS 和 JavaScript 的方式。他们同时上线两种变体,让数百万用户用自己的加载时间来投票。
这套系统的规模怎么说都不为过。OpenAI 在 Statsig 官网上的引言提到,他们在功能开关背后发布了超过 600 个功能;他们的数据工程团队描述的是 “数亿用户中同时进行的数百个实验”。Codex 上线时,一位工程师形容发布之夜的操作是先部署单体应用,然后 “打开开关”。再到 2025 年 9 月,OpenAI 以 11 亿美元收购了 Statsig,并让其创始人 Vijaye Raji 出任应用 CTO,掌管 ChatGPT 和 Codex 的工程团队。他们太喜欢这个功能开关工具了,干脆把公司买了下来,还让那个团队来负责产品。我想不出比这更响亮的声明,来表达实验体系在他们构建方式中的核心地位。

向十亿陌生人敞开大门
到目前为止,前面每一节都在赞美同一件事:无需账户,直接开始打字。这扇敞开的大门就是整个战略,但它也有代价 —— 前文一直没有正面讨论。如果地球上任何人都能免费、无需登录地调用前沿模型,那么地球上任何人也能写个脚本来对着它猛薅。免费的匿名推理就是一块吸引机器人的磁铁,每一次滥用请求都是真金白银的 GPU 算力在流失。所以真正重要的问题不是 ChatGPT 为什么允许你不登录就使用,而是它如何在每天数十亿次这样的调用中存活下来。
答案是一套几乎完全隐形的安全层 —— 而这正是精髓所在。在你发出第一条消息之前,两件事已经在后台悄然完成。Cloudflare 运行一个工作量证明(proof-of-work)挑战 —— 页面加载的那一刻你就能在网络面板中看到 cdn-cgi/challenge-platform 请求被触发,迫使每个客户端消耗一点 CPU 来证明自己是一个真实的浏览器。与此同时,OpenAI 自研的反滥用系统 —— 他们称之为 Sentinel—— 在一个沙箱化的 iframe 中启动(sentinel/frame.html,配有独立版本管理的 sentinel/sdk.js),从而在与其保护的应用完全隔离的环境中对客户端进行指纹识别。
其实我们在上一节已经见到了它的前端表现:应用在你发送任何内容之前就会触发 chat-requirements 的 prepare 和 finalize 握手。它的真正用途是这样的 —— 在你还在打字的时候就提前完成滥用检查,这样当你按下回车时,门卫早已核验完你的身份。这和应用中所有其他设计如出一辙:把昂贵的操作提前完成,隐藏在用户正在做的事情背后,让用户真正在意的那个瞬间感觉毫无延迟。机器人防御是预付的,和身份验证检查、数据预取一模一样。
而且这套系统相当深入。有研究人员解密了 chatgpt.com 上 Cloudflare 的挑战机制,发现它在判定你是否为真人之前,会检查运行中页面的数十个属性 —— 包括应用自身的 React Router 状态。不论这让你感到欣慰还是不安,这都是一个有益的提醒:在一个陌生人和他的第一个免费 token 之间,横亘着多少精密的机器,以及团队付出了多大的努力来确保这个陌生人永远不会感受到它们的存在。
// 在你还没打一个字之前,这些请求已在后台触发
GET /cdn-cgi/challenge-platform/... Cloudflare 工作量证明
GET /backend-api/sentinel/sdk.js 反滥用 SDK
sentinel/frame.html 在沙箱 iframe 中加载
POST /backend-anon/sentinel/chat-requirements/prepare
POST /backend-anon/sentinel/chat-requirements/finalize
这就是 “无需登录,直接输入” 不为人知的另一半。
这扇敞开的大门并不意味着安全的缺席。它是一扇无摩擦的门,背后焊接着消费级互联网上最强悍的机器人防御体系之一 —— 精心调校到让代价只落在脚本身上,永远不波及真人。Anthropic 解决同一个问题的方式是:干脆不开这扇门。OpenAI 选择把门敞着,把门卫藏起来,而本节中的几乎一切,都是为了让你永远察觉不到他的存在。
通往首个 Token 的最短路径
前面所有的一切,最终都服务于一个交互:你按下回车,token 开始出现。以下是从网络视角看,一个未登录用户经历的完整过程。
在你还在阅读页面的时候,应用已经调用了 /backend-anon/conversation/init,并通过 Sentinel—— 他们的反滥用层 —— 完成了 chat-requirements 检查,这一切都发生在你提交任何内容之前。当你按下回车,一个快速的 prepare 调用确认条件仍然满足,然后消息发出:
POST /backend-anon/f/conversation
content-type: application/json
200 OK
content-type: text/event-stream
回答是一个基于 POST fetch 的服务器发送事件(SSE)流 —— 和他们自 2022 年以来一直使用的模式完全一样 ——token 在到达的同时被渲染到已经绘制好的外壳中。传输层刻意做得 “无聊”。从预付的滥用检查到流式外壳,整个架构的编排只为了一件事:当你终于按下回车时,你唯一在等待的,就是模型本身。
连请求体都经过了周到的设计。其中包含一个 client_contextual_info 对象,携带了你的视口尺寸、设备像素比、暗色模式状态以及页面加载后的秒数,这样后端就知道它正在为怎样的画布渲染内容,或者进一步记录关键数据以供后续分析。
ChatGPT 与 Claude 的差异
将 ChatGPT 的 Web 应用与 Claude 的技术取舍做一番对比,两家公司的战略重心便一览无余。打开 chatgpt.com,无需账户,几秒钟内即可开始对话 —— 那些免费的匿名回答,花的是 OpenAI 实打实的 GPU 真金白银(OpenAI 已经开始通过广告来抵消这部分成本)。打开 claude.ai,迎面而来的是登录墙和引导流程,一个字都打不出来。Anthropic 选择了企业市场,这个选择贯穿了他们所有的工程取舍,正如 OpenAI 的消费者赌注贯穿了他们的一切。
你不得不敬佩专注所带来的简化力量。Anthropic 的选择为他们换来了更简单的问题:每个用户都经过认证,被攻击面更小,也不需要为十亿陌生人服务端渲染一个免费的试验场。这一点甚至体现在技术栈上:claude.ai 是一个客户端渲染的单页应用,直接从 CDN 分发 —— 当每个用户本来就要登录时,这是一个完全合理的选择(与 Linear 坚持客户端渲染的逻辑如出一辙)。OpenAI 的选择则意味着要吞下巨大的复杂性(机器人防御、挑战流程、匿名速率限制、一整套并行的 backend-anon API 层),只为从陌生人的第一次提问中抹去每一丝摩擦。两者都没有错。但你可以直接从各自的网络面板中读出每家公司的战略 —— 这大概是我写过的最 performance.dev 的一句话了。
ChatGPT Web 的构建全貌
大致轮廓是这样的。约束条件是一个使用未知设备的全新用户,因此应用在服务端渲染出完整的外壳,从边缘节点流式传输,首字节时间控制在 100 毫秒以内。框架是 React Router 7 的框架模式,经由 2024 年从 Next.js 迁移到 Remix、再随合并而来。样式采用 Tailwind v4,编译在一套设计令牌层之上,按路由拆分,不使用 Web 字体。组件层是 Radix 基础原语加上 ProseMirror 输入框。客户端数据使用 TanStack Query,由服务端预注入。启动过程是 160 个内容哈希分块,围绕你能开始打字的那一刻精心编排。每一个决策 —— 包括页面如何加载自身 —— 都由 556 个服务端求值的功能开关控制。最终的回报是:当模型有话要说的瞬间,回答便开始流式呈现。
最让我感触的是,这一切有多么标准 —— 是那种最好意义上的标准。Linear 的速度来自他们从第一天就开始构建的自研同步引擎。Conductor 的速度来自原生外壳和本地数据库。而 ChatGPT 的速度,来自前端领域最主流的技术栈 —— 只不过每一个默认值都被逐一质疑和度量过。在服务端求值功能开关、内联主题脚本、哈希分块名称、将一切延迟到输入框可用之后、从首字节开始度量。
如果你从事 Web 开发,打开 chatgpt.com 的开发者工具,四处探索一番。这是一堂免费的课 —— 教你如何把一个出色的 React 应用交付给互联网上的每一个人。
关于Python学习指南
如果你对Python感兴趣,想通过学习Python获取更高的薪资,那下面这套Python学习资料一定对你有用!
资料包括:Python安装包+激活码、Python web开发,Python爬虫,Python数据分析,人工智能、机器学习等学习教程。0基础小白也能听懂、看懂,跟着教程走,带你从零基础系统性地学好Python!
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、Python学习软件
工欲善其事,必先利其器。学习Python常用的开发软件都在这里了!
三、Python操作实例
学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。
因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。
四、Python就业项目实战
我们学习Python必然是为了找到高薪的工作或者高报酬的兼职,下面是一些公司所能用到的实战项目,学完这些相信大家一定可以找到满意的工作。

领取方式我会放在下面,希望领取了的朋友不要忘了(下方名片,放心添加*)

更多推荐



所有评论(0)