打开 ChatGPT,输入问题,等待回答出现。这段短暂的交互背后,涉及首屏渲染、组件初始化、安全校验、请求发送和流式内容渲染。
ChatGPT 网页版有两个性能目标:打开页面后尽快输入,发送消息后尽快看到回答。
本文翻译整理自 Dennis Brotzky 发布于 performance.dev 的 《Reverse Engineering ChatGPT Web: How OpenAI Built for a Billion Users》。原文基于公开的页面代码和网络请求展开分析,涉及架构的内容来自外部观察与推断,并非 OpenAI 官方说明。
未登录访问 ChatGPT,并提交一次完整提问。
01 用户范围与架构取舍
ChatGPT 的用户来自不同地区,使用的设备和网络条件也各不相同。网页既要照顾弱网环境下首次使用 ChatGPT 的用户,也要兼顾在高性能设备上长期使用的重度用户。
页面如果完全依赖客户端渲染,用户必须等待 JavaScript 下载、解析和执行,之后才能看到有效内容。服务器端渲染会将页面结构和主要内容写入首个 HTML 响应。浏览器收到文档后即可开始渲染页面,无需等待全部 JavaScript 下载和执行,有助于缩短首次内容绘制(FCP)的时间,也能让最大内容绘制(LCP)元素更早被发现和渲染。搜索引擎可以直接读取完整页面,对 SEO 也更友好。
浏览器收到页面后,还要完成 hydration(让服务端生成的 HTML 获得交互能力)。这个过程将服务器输出的页面与客户端代码连接起来,输入、点击和页面更新随后才能正常工作。
服务端渲染会增加服务器开销和工程复杂度。对应到技术实现,ChatGPT 网页版使用 React 19 渲染界面,React Router 7 负责路由与流式服务端渲染,Vite 负责构建。
02 前端框架演进:从 Next.js 到 React Router
ChatGPT 的前端框架经历了三个阶段:Next.js Pages Router、Remix,以及现在的 React Router 7。框架迁移的同时,页面的渲染方式也在不断调整。
ChatGPT 于 2022 年 11 月 30 日上线,最初使用 Next.js 12 和 Pages Router。Pages Router 根据文件目录生成页面路由,是当时 Next.js 已经成熟的路由方案。这套架构持续使用了约 21 个月。其间,Next.js 推出了以 React Server Components 为基础的 App Router,但 ChatGPT 没有采用。
2024 年 8 月 27 日,OpenAI 开始向少量用户提供基于 Remix 构建的版本。到 9 月初,Remix 版本已经大范围上线。Remix 将路由、数据读取和服务端渲染放在同一套框架中,不过 ChatGPT 主要使用了其中的路由和数据传递能力。
在这一阶段,服务端使用 Express 处理请求。用户访问页面时,路由对应的 loader 会读取所需数据,再将结果序列化到 window.__remixContext。这个全局变量相当于服务器交给浏览器的初始化数据,客户端可以直接使用,不必在页面启动后重新请求一遍。
服务器此时生成的 HTML 仍然比较有限,主要包含应用外壳、资源预加载链接和主题脚本,大部分界面由浏览器完成渲染。TanStack Query 继续负责客户端的数据请求与缓存,创建对话、发送消息等操作则通过 ChatGPT 自有的 API 完成。
2024 年 11 月,Remix 的框架能力并入 React Router 7。React Router 由客户端路由库扩展为同时支持路由数据读取和服务端渲染的完整框架,ChatGPT 随后也迁移到这一版本。
当前页面源码中可以看到下面的配置:
window.__reactRouterContext = {
ssr: true,
isSpaMode: false,
// ...
}
ssr: true 表示页面启用了服务端渲染,服务器会直接生成 HTML。isSpaMode: false 表示应用没有采用纯单页应用模式,页面内容并非全部等待浏览器中的 JavaScript 生成。
页面源码中的 ssr: true 和 isSpaMode: false。
相比早期 Remix 版本,现在的服务器会生成未登录页面的完整内容,包括侧边栏、欢迎语和输入框。浏览器收到 HTML 后可以立即开始渲染,再通过 hydration 接入交互能力。
React Router 还统一承载了聊天界面、产品介绍和营销页面。客户端的路由清单包含 354 条路由,其中约 13 条属于聊天功能。其余页面与聊天界面共用路由、设计系统、Feature Flag 和部署流程。用户从产品页面进入聊天界面时,只需加载目标路由所需的资源,不必重新加载整个网站。
这段演进伴随着服务器职责的变化:Remix 早期主要返回应用外壳,React Router 7 则负责输出完整页面,并通过流式服务端渲染继续传递尚未完成的数据和内容。
03 首屏渲染与加载性能
首屏性能取决于浏览器何时收到可渲染的内容,以及这些内容需要经过多少处理才能显示。
在温哥华进行的一次测试中,未登录页面压缩后的 HTML 文档约为 84 KB。请求由附近的 Cloudflare 边缘节点响应,首字节时间(TTFB)约为 50~65 毫秒。TTFB 指浏览器发起请求后,收到服务器第一个字节所经历的时间。具体结果会受到地理位置和网络状况影响,但这组数据可以反映 ChatGPT 的页面分发速度。
这份 HTML 已经包含侧边栏、欢迎语、输入框等首屏内容,其中实际页面标记约为 30 KB。完成 hydration 后,整个页面约有 548 个 DOM 节点。较少的页面元素可以降低浏览器解析 HTML、计算样式和生成布局的工作量。
关闭 JavaScript 后,服务端生成的页面结构仍然可以显示。
页面开始渲染前,一段内联脚本会读取用户保存的主题设置,并立即为 HTML 根元素添加浅色或深色样式:
!function () {
try {
var d = document.documentElement;
var c = d.classList;
c.remove('light', 'dark');
var e = localStorage.getItem('theme');
if (e === 'system' || (!e && true)) {
var t = '(prefers-color-scheme: dark)';
var 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) {}
}();
这段逻辑可以避免页面先显示默认主题,再突然切换颜色。主题判断直接写在 HTML 中,也不需要等待外部 JavaScript 文件加载。
页面还记录了 HTML 开始执行和首个画面出现的时间:
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();
});
这些数据会被发送到真实用户监控系统。真实用户监控(RUM)收集用户设备和网络环境中的实际性能数据,比单一实验室环境更能反映页面的加载体验。
ChatGPT 使用 React 19 和 React Router 7 提供流式服务端渲染。服务器不必等所有数据准备完成后再一次性返回页面,而是先发送基础页面结构。尚未完成的部分由 React Suspense 暂时保留,数据准备完成后,再通过后续的 HTML 片段和内联脚本填入对应位置。
路由数据也通过独立的 ReadableStream 与 HTML 并行传输。ReadableStream 允许浏览器边接收边处理数据,减少等待完整响应结束的时间。服务器还会在返回的数据中加入预加载指令,例如是否提前获取模型列表、历史记录和收藏对话。客户端根据这些指令准备后续资源,避免所有用户执行同一组请求。
未登录页面不会等待账号状态检查完成后再开始渲染。ChatGPT 为匿名用户提供了独立的数据接口,页面可以先显示输入框,身份与使用额度等信息随后补充。匿名访问涉及的安全校验和风控机制,将在后面的章节单独说明。
这套加载流程将首屏任务分成了几个阶段:先返回可以显示的 HTML,再让输入框获得交互能力,最后加载历史记录、代码渲染等非关键功能。用户看到页面和开始输入的时间,不会被全部应用资源阻塞。
04 样式系统:主题变量与按需加载
ChatGPT 使用 Tailwind CSS 管理页面样式。Tailwind 会在构建阶段生成静态 CSS,服务器返回 HTML 时只需输出对应的类名,浏览器不必等待 JavaScript 在运行时生成样式。相比 CSS-in-JS(由 JavaScript 在运行时生成样式),这种方式更容易配合服务端渲染,也减少了客户端执行和 hydration 的工作量。
页面元素上的类名带有明显的 Tailwind 特征:
<div class="bg-token-main-surface-primary border-token-border-light
flex h-svh w-screen flex-col @container/thread">
其中,flex、h-svh 和 w-screen 是常见的 Tailwind 工具类;bg-token-main-surface-primary 和 border-token-border-light 则来自 ChatGPT 的主题变量体系。
这些类名不会直接绑定某个颜色值,而是引用具有明确用途的 CSS 变量。例如,main-surface-primary 表示页面的主要背景区域。浅色、深色和自定义主题分别为这些变量提供不同的颜色。切换主题时,浏览器只需更新变量值,不需要修改组件结构或重新执行组件逻辑。
自定义聊天主题的处理方式与深色模式相似。页面渲染前,内联脚本会从 localStorage 读取用户保存的主题,并将结果写入 HTML 元素的属性。首屏样式从一开始就是正确的,可以避免页面加载后再次切换颜色。
ChatGPT 没有把所有 CSS 合并成一个文件。基础样式和聊天界面样式会在进入页面时加载,代码块、思维过程和全局弹窗等样式则分别放在 code-block.css、cot-message.css 和 global-modals.css 中。相关功能出现时,浏览器才会下载对应的文件。
代码块、弹窗和推理过程等功能拥有独立的 CSS 文件。
这种拆分方式可以减少首屏需要下载和解析的 CSS。代码块尚未出现在回答中时,页面不需要提前加载代码编辑器的完整样式;用户没有打开弹窗时,弹窗样式也不会占用首屏资源。
字体采用了同样的按需策略。普通界面文字直接使用设备自带的系统字体:苹果设备使用 SF Pro,Windows 使用 Segoe UI,Android 使用 Roboto。页面不需要额外下载一套正文字体,也能减少字体替换造成的布局变化。
只有品牌文字会加载一个体积较小的半粗体字体文件。KaTeX 数学字体也只在回答包含公式时下载。首屏只加载基础样式并使用系统字体,其余样式和字体在相关功能出现时再加载。
05 组件库与编辑器实现
ChatGPT 的前端大量使用成熟的第三方库,主要包括 Radix UI、ProseMirror、CodeMirror 6 和 TanStack Query。Radix UI 是 Headless 组件库,只提供组件的交互逻辑和无障碍能力,不规定视觉样式;ProseMirror 是富文本编辑器框架;CodeMirror 6 面向代码展示与编辑;TanStack Query 负责客户端的数据请求和缓存。
菜单、下拉选择、提示消息、滚动区域和浮层等常见组件主要由 Radix UI 提供。它负责键盘操作、焦点管理、ARIA 属性和浮层定位,组件外观则由 Tailwind CSS 和主题变量控制。页面既能保持统一的视觉风格,也能复用经过浏览器兼容性和无障碍验证的交互能力。
开发者工具可以在下拉菜单上观察到 Radix UI 生成的属性。
ChatGPT 的输入框由 ProseMirror 驱动,是一个完整的富文本编辑器。除了普通文本输入,它还要处理光标位置、文本选择、格式、附件、提及和嵌入内容等交互,普通的 <textarea> 很难覆盖这些需求。
ProseMirror 加载完成前,服务器会先生成一个外观相同的静态输入区域。输入框可以随首屏 HTML 一起显示,客户端脚本加载后再由 ProseMirror 接管。这样可以减少编辑器出现前的空白,也能避免初始化造成明显的界面跳动(降低 CLS,即累积布局偏移)。
聊天输入框由 ProseMirror 驱动。
回答中的代码块使用 CodeMirror 6。代码内容因此具备语法高亮、文本选择和复制等能力,ChatGPT 的 Canvas 代码编辑功能也使用了同一套编辑器。ProseMirror 和 CodeMirror 均由 Marijn Haverbeke 创建,OpenAI 也为他的开源工作提供赞助。
ChatGPT 中的编辑、搜索、图表和附件等交互界面。
从页面 DOM 可以看到,聊天消息没有使用虚拟列表,每条消息都会保留在页面中。虚拟列表只渲染当前可见的内容,可以减少超长页面的 DOM 节点,但也会增加滚动定位、浏览器页面搜索和无障碍访问的复杂度。
OpenAI 没有公开这一选择的具体依据。一种可能的解释是,大多数对话尚未长到必须虚拟化的程度,保留完整 DOM 还能继续使用浏览器原生的内容选择和页面搜索。
客户端数据与缓存
TanStack Query 管理浏览器中来自服务器的数据,包括请求状态、缓存和后续更新。ChatGPT 在迁移到 Remix 之前已经使用这套方案,迁移到 React Router 后仍然保留。
服务器返回的页面会初始化一个名为 window.__REACT_QUERY_CACHE__ 的全局缓存。服务端已经获取的数据会提前写入其中,浏览器完成 hydration 后可以直接读取,不必再次请求相同内容。
首屏数据由服务器提供,进入页面后的刷新、缓存和状态同步则交给 TanStack Query。服务端渲染与客户端数据管理由此衔接起来。
06 流式回答:从 Token 到页面内容
ChatGPT 不会等待模型生成完整回答后再一次性返回。连接建立后,服务器会持续发送新生成的内容,浏览器收到一部分便更新一部分。这里的 Token 可以理解为模型逐步生成的文本片段,它可能是一个字、一个词,也可能只是词语的一部分。
回答通过 SSE 流传递。SSE 是一种由服务器持续向浏览器发送事件的通信方式,连接在回答生成期间保持打开,前端可以不断接收新增内容。具体的请求路径和连接过程会在后面的章节展开。
对前端来说,收到 Token 只是第一步。回答通常包含 Markdown,新的内容到达后,前端需要重新解析不断增长的文本,再更新页面中的对应部分。生成速度越快,解析和渲染发生得越频繁。
流式内容很少处于完整状态。例如,加粗标记可能只收到开头的 **,代码块可能缺少结束符,表格可能只生成了前几行。Markdown 解析器需要在内容不完整时维持可用的页面结构,并在后续 Token 到达后继续修正。
**正在生成的重点内容
```js
const result = await fetch(
上面的加粗语法和代码块都没有结束。随着回答继续生成,解析结果可能从普通文本变为加粗内容或完整代码块。前端需要控制这一过程产生的重新渲染和布局变化,避免页面频繁跳动。
代码块完成识别后,会由 CodeMirror 6 负责展示。代码编辑器相关的 JavaScript 和 CSS 可以在回答需要代码块时再加载,不必进入聊天页面时全部下载。语法高亮、文本选择和复制功能也由 CodeMirror 提供。
回答中的代码块由 CodeMirror 6 渲染。
数学公式通过 KaTeX 渲染。页面会同时生成两份内容:一份是用户看到的公式,另一份是隐藏的 MathML 结构。MathML 使用浏览器和辅助技术能够理解的数学标记,使屏幕阅读器可以朗读公式,复制时也能保留数学含义。
KaTeX 负责视觉渲染,MathML 为辅助技术保留公式结构。
流式回答还会持续改变消息高度和页面滚动位置。前端需要在内容追加、Markdown 结构变化和用户主动滚动之间保持稳定,避免新 Token 到达时打断阅读。
从 Token 到最终页面,需要依次经过接收、文本拼接、Markdown 解析、组件渲染和布局更新。回答生成期间,这条链路会反复执行,渲染效率直接影响用户感受到的流畅度。
07 优先让输入框可用
从一次未登录、没有可复用缓存的加载记录看,ChatGPT 最终会请求超过 100 个 JavaScript 文件。这些文件并非一个完整代码包,而是由构建工具拆分出的多个模块,通常称为 Chunk。不同路由和功能可以分别加载自己的 Chunk,避免首屏下载整套应用代码。
虽然文件数量很多,HTML 只通过 modulepreload 提前加载其中 14 个关键模块,包括应用入口、React 公共代码和当前路由所需的模块。
modulepreload 是浏览器提供的资源提示。它会让浏览器提前获取指定的 JavaScript 模块,避免等到应用运行后才发现这些依赖。只有输入框启动所需的代码进入这组高优先级资源,其余模块按照使用时机继续加载。
ChatGPT 的启动配置中还包含一个名称非常直接的 Feature Flag:
deferStartupImportsUntilComposerTTFI
Composer 指聊天输入框,deferStartupImports 表示推迟启动阶段的其他模块。这个配置会将非关键代码的加载延后到输入框进入可交互状态之后,避免数据分析、弹窗或其他功能占用启动阶段的执行时间。
聊天界面的核心模块名为 conversation-small。它包含新用户开始对话所需的基础能力,并被大量聊天路由共同引用。代码块、思维过程和其他复杂内容分别放在独立模块中,回答需要这些功能时再进行加载。
页面资源基本来自 chatgpt.com/cdn/assets,并通过 Cloudflare 分发。JavaScript、CSS 和字体使用同一站点的资源地址,可以减少访问新域名时产生的 DNS 查询、TLS 连接和跨域检查。静态资源还带有较长的 HTTP 缓存时间,用户再次访问时可以复用已经下载的文件。
页面中没有观察到 Service Worker。Service Worker 可以在浏览器后台拦截请求并缓存应用资源,常用于离线访问和更激进的资源预缓存。ChatGPT 目前主要依赖标准 HTTP 缓存。
OpenAI 没有公开不使用 Service Worker 的具体原因。一种可能的考虑是,ChatGPT 必须连接服务器和模型才能完成对话,离线缓存的价值有限;频繁发布新版本时,过期的 Service Worker 也可能让用户继续运行旧代码。
整个启动过程由此形成明确的加载顺序:HTML 先显示页面,关键模块让输入框获得交互能力,其余功能随后加载。首屏不需要等待完整应用准备完成,用户可以更早开始输入。
页面优先完成输入框启动,其余模块随后加载。
08 Feature Flag:灰度发布与 A/B Testing
Statsig 是一套面向产品和工程团队的实验平台,主要用于管理 Feature Flag、A/B Testing 和动态配置。开发者可以让同一份代码对不同用户启用不同功能,再通过数据比较各个版本的效果,无需为每次调整单独发布前端代码。
ChatGPT 将 Statsig 接入页面的启动流程。服务器返回的 HTML 中包含一份客户端启动配置。在一次页面分析中,这段 JSON 约为 377 KB,除了登录状态、语言和地区,还包含 556 个 Feature Flag、144 份动态配置和 192 个实验层。
Feature Flag 负责控制某项能力是否启用,也可以按照用户、账号状态或地区逐步扩大开放范围;动态配置用于下发加载数量、超时时间或界面设置等参数;实验层用于协调多个实验,避免同一用户同时进入互相冲突的测试。这里的数量来自当时的页面快照,后续会随实验调整而变化。
配置中的实验数据大致采用下面的结构:
{
"statsigPayload": {
"feature_gates": {
"3479398748": {
"name": "3479398748",
"rule_id": "6cYbYFM2vjVEPNwAxdQvEB:100.00:3",
"value": true
}
},
"dynamic_configs": {},
"layer_configs": {}
}
}
页面源码中包含由服务器计算并写入的 Statsig 配置。
这些 Feature Flag 在服务器端完成计算。服务器会根据用户的匿名 ID、账号状态和所在地区确定实验结果,再将配置直接写入 HTML。React 开始运行时,每个 Feature Flag 已经有了明确结果。
如果浏览器启动后再向实验平台请求配置,页面需要等待额外的网络响应。另一种做法是先显示默认版本,配置返回后再切换,但可能造成界面闪动。服务端预先计算并写入 HTML,可以避开这两种情况。
Feature Flag 的名称经过哈希处理,所以页面中看到的是 3479398748 这样的数字,而不是可读的功能名称。这样可以减少未发布功能通过前端代码提前泄露,但也降低了外部分析这些字段的可能性。
实验数据通过 chatgpt.com/ces/v1/ 发送,而不是直接请求 Statsig 的第三方域名。与其他静态资源相似,这些请求也走 ChatGPT 自己的域名,可以减少跨域连接,并降低第三方请求被浏览器扩展拦截后产生的数据缺口。
实验系统还会控制页面自身的加载方式。除了上一章提到的 deferStartupImportsUntilComposerTTFI,配置中还能看到 promoteCss 和 stripModulepreloadImports。从名称可以看出,它们分别用于调整 CSS 的加载优先级,以及改变 modulepreload 中包含的模块。
同一套前端代码可以同时保留多种加载策略,再通过真实用户的性能数据比较结果。首屏资源如何排序、哪些模块应当延迟,并不只依靠开发阶段的判断,也会进入持续的 A/B Testing。
Statsig 在这套发布流程中占据重要位置。2025 年 9 月,OpenAI 宣布收购 Statsig,其创始人 Vijaye Raji 随后担任应用部门 CTO,负责 ChatGPT、Codex 等产品的工程工作。
09 匿名访问与风控机制
ChatGPT 允许用户在没有账号的情况下直接提问。省去登录和注册步骤可以降低使用门槛,同时也为自动化脚本提供了入口。每一次匿名请求都会消耗模型推理资源,因此网页需要在不打断正常操作的情况下完成机器人识别、请求限流和滥用拦截。
页面加载后,ChatGPT 会为匿名用户分配一个用户 ID。这个 ID 不需要关联真实账号,但可以用于请求限流、Feature Flag 分组和 A/B Testing。匿名页面通过一组独立接口获取用户状态、模型列表和对话信息:
/backend-anon/me
/backend-anon/models
/backend-anon/conversation
用户尚未开始输入时,Cloudflare 的浏览器验证已经在后台运行。页面会请求 cdn-cgi/challenge-platform,让客户端完成一组环境检查和计算任务。正常浏览器可以在后台完成这些操作,批量自动化脚本则需要承担额外的计算和模拟成本。
ChatGPT 还使用一套名为 Sentinel 的风控系统。Sentinel 的 SDK 会在一个沙箱 iframe 中启动:
/backend-api/sentinel/sdk.js
sentinel/frame.html
iframe 为 Sentinel 提供独立的运行环境。它可以收集浏览器环境和页面运行状态,用于判断请求是否来自正常客户端,同时减少风控代码与主应用之间的直接影响。
发送消息前,页面还会调用 chat-requirements 接口。整个过程分为 prepare 和 finalize 两个阶段:
POST /backend-anon/sentinel/chat-requirements/prepare
POST /backend-anon/sentinel/chat-requirements/finalize
prepare 会提前准备发送消息所需的验证信息,finalize 则在提交前完成确认。这些请求会在用户阅读页面和输入内容期间执行。按下发送按钮时,大部分校验已经结束,消息请求不必再等待完整的验证流程。
ChatGPT 将风控校验前移到页面加载和用户输入阶段。环境分析、身份标识和请求验证在后台完成,用户仍然可以直接进入页面并开始提问。
10 从发送消息到第一个 Token
用户发送消息后,到第一段回答出现在屏幕上的时间,通常称为首 Token 延迟(Time to First Token,TTFT)。这段时间不仅包含模型生成,还包括风控确认、请求传输、服务端处理和前端渲染。
在用户输入之前,ChatGPT 已经调用 /backend-anon/conversation/init,为匿名对话准备所需状态。上一章提到的 Sentinel 和 chat-requirements 也会提前完成大部分风控检查。
整个请求过程如下:
sequenceDiagram
participant U as 用户
participant B as 浏览器
participant R as 风控服务
participant S as 对话服务
participant M as 模型
B->>S: 页面加载,初始化匿名对话
B->>R: prepare / finalize
Note over B,R: 用户阅读页面和输入期间提前完成
U->>B: 发送消息
B->>R: 快速确认 chat-requirements
R-->>B: 校验通过
B->>S: POST /backend-anon/f/conversation
S->>M: 提交对话内容
M-->>S: 生成第一个 Token
S-->>B: 通过 SSE 返回第一个 Token
B-->>U: 解析并渲染回答
loop 回答尚未结束
M-->>S: 继续生成 Token
S-->>B: 持续发送 SSE 事件
B-->>U: 更新页面内容
end
用户按下发送按钮后,页面会快速确认 chat-requirements 仍然有效,随后发出对话请求:
POST /backend-anon/f/conversation
content-type: application/json
服务器成功接收请求后,返回的内容类型为:
200 OK
content-type: text/event-stream
text/event-stream 表示响应不会一次性返回完整结果。服务器保持连接,并随着模型生成不断发送新的事件。浏览器通过 Fetch API 读取响应流,再将事件中的内容交给前端渲染。
这里没有直接使用浏览器的 EventSource。EventSource 主要用于 GET 请求,而发送消息需要通过 POST 提交提示词、对话状态和其他参数。使用 Fetch 发起 POST,同时读取 SSE 格式的响应,可以在一个请求中完成数据提交和流式接收。
请求体中还包含一个名为 client_contextual_info 的对象。它记录视口尺寸、设备像素比、深色模式状态,以及页面加载后经过的时间。OpenAI 没有公开这些字段的具体用途,它们可能用于界面适配、性能分析或请求日志。
发送消息前完成风控确认,随后通过 SSE 接收流式回答。
收到第一段 SSE 数据后,前端会将内容写入当前消息,再进入前面介绍的 Markdown 解析和组件渲染流程。第一个 Token 显示后,同一条连接继续传递剩余内容,直到回答结束。
前几章中的优化都作用在这条路径上:HTML 提前显示输入框,关键模块优先加载,风控在用户输入期间执行,匿名对话提前初始化。发送消息时,浏览器需要额外完成的准备工作已经很少,首 Token 延迟主要由网络传输、服务端调度和模型生成决定。
11 开放入口与账号入口:ChatGPT 和 Claude 的不同选择
ChatGPT 和 Claude 都提供网页聊天界面,但两者对首次使用的设计不同。在支持的地区,用户打开 ChatGPT 后可以不登录直接提问;Claude 的网页端则先要求用户通过邮箱或 Google 账号完成登录。
入口方式会影响后续的前端和服务端设计。
ChatGPT 需要在没有账号信息的情况下显示输入框、创建对话并发送消息。为此,网页端提供了独立的匿名接口,并为未登录用户分配匿名 ID。Feature Flag、请求限流和风控校验也要在匿名状态下运行。
开放入口缩短了用户从进入网站到开始提问的路径,同时增加了系统成本。自动化脚本可以直接访问匿名接口,每次滥用请求还会消耗模型推理资源。前面介绍的 Cloudflare、Sentinel 和 chat-requirements,都与这一入口策略有关。
Claude 将账号验证放在聊天界面之前。用户完成登录后,系统已经获得稳定的身份信息,可以直接关联使用额度、订阅状态、历史记录和组织权限。账号入口也为请求限流和异常行为识别提供了更明确的依据。
账号入口将匿名流量挡在聊天界面之外,用户需要先完成登录或注册。首次使用路径更长,进入应用后则可以直接围绕账号状态加载数据和功能。
两者的架构差异来自不同的产品入口:
| 入口设计 | ChatGPT | Claude |
|---|---|---|
| 首次使用 | 支持地区可免登录提问 | 登录后开始使用 |
| 进入聊天前 | 可以直接看到输入框 | 先完成身份验证 |
| 初始用户状态 | 使用匿名 ID | 使用账号身份 |
ChatGPT 提供免登录入口,Claude 先进入账号登录流程。
ChatGPT 为更短的首次使用路径承担了额外的匿名接口、风控和推理成本。Claude 通过账号入口获得稳定的用户身份,也接受了登录步骤带来的使用门槛。
技术架构没有银弹。ChatGPT 用更复杂的匿名访问和风控体系降低使用门槛,Claude 通过账号入口建立稳定的身份、权限和订阅关系。两种选择对应不同的产品形态与业务价值。
12 ChatGPT 网页版架构总结
根据公开的页面资源和网络请求,ChatGPT 网页版的整体架构可以概括为:
sequenceDiagram
participant U as 用户
participant C as Cloudflare
participant F as Statsig
participant S as React Router 7
participant B as 浏览器
participant R as Sentinel
participant A as 对话接口
participant M as 模型
participant P as 性能监控
U->>C: 打开 ChatGPT
C->>C: 资源分发与浏览器验证
C->>S: 请求页面
F-->>S: Feature Flag 与 A/B Testing 配置
S-->>B: React 19 流式 SSR 内容
B->>B: Hydration 与客户端初始化
B-->>P: 上报真实用户性能数据
B->>R: 初始化匿名访问与风控
R-->>B: 返回访问校验结果
U->>B: 输入并发送消息
B->>R: 请求会话风控校验
R-->>B: 校验通过
B->>A: 提交对话请求
A->>M: 提交上下文并请求推理
M-->>A: 生成第一个 Token
A-->>B: 通过 SSE 返回 Token
B-->>U: 渲染 Markdown、代码与公式
loop 回答尚未结束
M-->>A: 继续生成 Token
A-->>B: 持续发送 SSE 事件
B-->>U: 增量更新回答
end
Vite 将 JavaScript 和 CSS 按路由与功能拆分,Cloudflare 负责资源分发。React Router 7 和 React 19 先返回可以显示的 HTML,再由浏览器完成 hydration。ProseMirror、Radix UI、Tailwind CSS 和 TanStack Query 分别处理输入、基础交互、样式和客户端数据。
用户输入期间,Sentinel 和 chat-requirements 提前完成风控校验。消息通过 POST 请求发送,回答以 SSE 数据流返回,再由 Markdown、CodeMirror 和 KaTeX 持续渲染。
Statsig 将 Feature Flag 和 A/B Testing 结果写入 HTML,真实用户监控则记录加载与交互性能。技术栈以成熟的开源方案为主,性能优化集中在资源加载顺序和任务执行时机。
这些技术选择最终改善的是用户能够直接感知的体验。页面打开后,用户无需等待完整应用加载就能开始输入;消息发出后,第一个 Token 尽早出现,让用户确认回答已经开始生成。