openai

Every chatgpt.com page carries 377 KB of flag decisions

Promtime

openai

There is a server-side switch in ChatGPT's bootstrap config called deferStartupImportsUntilComposerTTFI: hold the startup imports until the chat box is interactive. Dennis Brotzky found it by digging through page source, bundles and network requests from the outside, and published the results on Performance.

At a glance

  • The logged-out document comes in at 84 KB compressed with a first byte around 50 to 65 ms from a nearby Cloudflare edge, and hydrates into a page of 548 DOM nodes.
  • Every page carries a 377 KB JSON bootstrap with the experiment state evaluated per request: 556 feature gates, 144 dynamic configs and 192 experiment layers, all named as hashed numbers.
  • The whole teardown is outside-in: Brotzky says he does not work at OpenAI and has never seen the source, so the reasons behind each choice are read off artifacts.

If you have not followed the series, this is the latest outside-in teardown by Brotzky; according to performance.dev, the site published a Linear breakdown in May 2026 and a Conductor one in June 2026. He assembled this one from the HTML, JavaScript, CSS and network requests of chatgpt.com plus talks, tweets and the careers page. The experience he is measuring is served to roughly 1 billion people.

The move off Next.js was spotted in the wild, not announced

ChatGPT launched on 30 November 2022 as a Next.js 12 app on the Pages Router, shipped inside OpenAI as a research preview around a fine-tuned GPT-3.5. The launch build manifest, still in the Wayback Machine, lists nine paths, among them /codespace and /workspace. The app stayed on the Pages Router for about 21 months and never adopted the App Router.

On 27 August 2024 Tibor Blaho noticed chatgpt.com serving a Remix build to some users; by 4 September it had rolled out widely, and Remix co-creator Ryan Florence tweeted that a new Remix app had dropped. Wes Bos went through the bundle a day later: an Express server ran the loaders and serialized roughly 7,000 lines of JSON into window.__remixContext, while rendering almost no UI.

When Remix v2 merged into React Router as v7 in November 2024, ChatGPT followed. The page source now reads ssr: true and isSpaMode: false, the full framework mode with streaming server render. The client manifest lists 354 routes, and only about 13 are the chat app; the rest are marketing and landing pages sharing one router, one design system and one deploy.

84 KB of HTML, 548 DOM nodes and no webfont

The logged-out document Brotzky measured is 84 KB compressed, with a first byte of 50 to 65 ms to his laptop in Vancouver. Inside it sit roughly 30 KB of real markup, the sidebar, the greeting and the composer, plus the styles to paint them. After hydration the page holds 548 DOM nodes.

The head does the more interesting work. An inline script reads the saved theme out of localStorage and applies it before first paint, so there is never a flash of the wrong one; a second does the same for the chat theme. A third timestamps when the HTML starts executing and the first frame after it, for every visitor, and feeds both into real user monitoring.

Styling is Tailwind over a semantic token layer, so light, dark and custom chat themes swap CSS variables instead of re-rendering components, and the CSS arrives in route-sized pieces such as code-block.css and global-modals.css. UI text uses the platform font stack; the KaTeX math fonts download only when an answer contains math.

A cold load pulls over a hundred JavaScript chunks, of which the document modulepreloads 14. Everything comes from chatgpt.com/cdn/assets behind Cloudflare with 30-day cache headers, so no third-party origin adds a DNS lookup or a TLS handshake. There is no service worker. The slim chat core ships under a readable name, conversation-small.

Code blocks inside answers are full CodeMirror editors

Open the DOM and it is data-radix attributes everywhere: menus, selects, toasts, popovers. The composer looks like a textarea and is a full ProseMirror editor, with the server rendering a static look-alike that ProseMirror hydrates over. OpenAI sponsors Marijn Haverbeke, who wrote both ProseMirror and CodeMirror.

Every code block in an answer is a CodeMirror instance, with .cm-editor and .cm-content in the DOM, highlighting and a copy button attached. Math renders through KaTeX and ships twice: the visual version you see and a hidden MathML tree under it, so a screen reader can read the formula out loud and you can copy it as math.

Client data runs on TanStack Query, seeded from a window.__REACT_QUERY_CACHE__ global in the document; Tanner Linsley said on Syntax that it predates the Remix move and was kept through it. The message list is not virtualized, so every message in a conversation stays in the DOM.

556 feature gates ride inside every document

A script tag in the served HTML holds 377 KB of JSON: auth status, locale, region, and a snapshot of the experimentation state computed per request against your anonymous ID and region. The day Brotzky checked, that meant 556 feature gates, 144 dynamic configs and 192 experiment layers, all answered before React hydrates.

The gate names are hashed numbers rather than readable strings, and event logging goes to chatgpt.com/ces/v1/ on OpenAI's own domain instead of Statsig's endpoints. Some flags point at the loading strategy itself: deferStartupImportsUntilComposerTTFI sits beside promoteCss and stripModulepreloadImports, server-side switches for how the document loads its own CSS and JavaScript.

An OpenAI quote on Statsig's site mentions launching over 600 features behind flags. In September 2025 OpenAI bought Statsig for 1.1 billion dollars and made founder Vijaye Raji CTO of Applications, running engineering for ChatGPT and Codex.

Sentinel and a Cloudflare challenge run before you press enter

Free anonymous inference is a bot magnet, so two things happen before your first message. Cloudflare fires a proof-of-work challenge from cdn-cgi/challenge-platform, making the client burn CPU to prove it is a real browser. OpenAI's anti-abuse layer, Sentinel, boots inside a sandboxed iframe with its own separately versioned SDK.

Then the app runs chat-requirements prepare and finalize against the parallel /backend-anon/ surface while you are still typing, so the bouncer checks your ID in the queue rather than at the door. Anonymous visitors get a real user ID there, which is how rate limits and experiments work without an account.

When you hit enter, the message goes out as a POST that answers with text/event-stream, the SSE pattern in use since 2022, and tokens render into the already painted shell. The body carries a client_contextual_info object with viewport size, pixel ratio, dark mode state and seconds since page load. Anthropic's claude.ai sits behind a login wall and ships as a client-side rendered SPA off a CDN.

All of this is read off artifacts. Brotzky says he never saw OpenAI's code, so the reasoning he attaches to the unvirtualized message list or the missing service worker is inference from the bundle, not something the company stated. In our view the boldest call is shipping 354 routes of marketing inside the product router, because a campaign landing page then rides the same deploy as the composer a billion people type into.

Where the next change shows up

Because the gate names are hashed numbers, the next redesign will surface in the document as a digit string rather than a label, which is the point of hashing them. The loading experiments are live right now: promoteCss and stripModulepreloadImports are being served to real users, and whichever variant wins will simply appear in the HTML one day. No timetable for that is given anywhere in the teardown.

Comments

No comments yet. Be the first.

Join the conversation

Sign in with Google to leave a comment. Your name and avatar come from your Google profile, and the comment appears after moderation.

We only use your name and avatar from Google. We never store your email address.

Every chatgpt.com page carries 377 KB of flag decisions · News