Every chatgpt.com page carries 377 KB of flag decisions

Dennis Brotzky spent days taking chatgpt.com apart from the outside and found a boot sequence built around one moment: when the chat input starts accepting keystrokes. In his July 2 breakdown for Performance, he traces it to a server bootstrap flag named deferStartupImportsUntilComposerTTFI, which holds back startup imports until the composer is interactive.
Brotzky doesn’t work at OpenAI and has never seen its source code, and OpenAI has published little about ChatGPT web. Everything below comes from the HTML, JavaScript, CSS and network requests chatgpt.com sends to any visitor, plus talks, tweets and job listings. What he found is a stack made almost entirely of standard libraries. The engineering effort goes into load order, server-side decisions and bot defense.
From Next.js to more server rendering
ChatGPT launched on November 30, 2022 as a Next.js 12 app on the Pages Router, a “research preview” wrapped around a fine-tuned GPT-3.5. The route manifest from that launch build, still in the Wayback Machine, lists nine pages, including /codespace and /workspace/[[...tid]]. Brotzky reads those as internal tools that shipped in the same build while the team raced the MVP out the door. The app never adopted the App Router. It stayed on the Pages Router for about 21 months and then left Next.js entirely.
OpenAI never announced the move. On August 27, 2024, Tibor Blaho spotted chatgpt.com serving a Remix build to a slice of users as an experiment. By September 4 it had rolled out widely, and Remix co-creator Ryan Florence tweeted: “New Remix app just dropped: chatgpt.com.”
The Remix-era app was mostly a client-side app inside a server shell. An Express server ran route loaders and serialized around 7,000 lines of JSON into window.__remixContext, but rendered almost no UI: a shell, some preload links, a theme script. It used no Remix actions, and mutations went through OpenAI’s own API. Florence later said “most of their data loading is done with TanStack Query instead of React Router’s loaders.” Evan You, who created Vite, summed up how the community read the switch: “Many of you probably just need an SPA too.”
When Remix v2 merged back into React Router as v7 in November 2024, ChatGPT followed. Today every page source carries window.__reactRouterContext with "ssr": true and "isSpaMode": false, which is React Router 7 in framework mode with streaming server rendering on Vite. The app now server-renders the entire logged-out experience as real HTML, so it does much more on the server than the empty Remix shell did. The client manifest lists 354 routes, and only about 13 belong to the chat app. The rest are marketing pages such as routes/($lang).codex.pricing, in the same codebase, router and deploy. Clicking from a campaign page into the chat is a client-side navigation.
The rest of the stack is just as mainstream: React 19, TypeScript, Tailwind CSS, Radix UI primitives, TanStack Query, Statsig for flags, Datadog for real user monitoring and Cloudflare in front. Brotzky contrasts it with Gemini’s frontend, which he calls “full of proprietary Google stuff.”
Can you type yet?
The logged-out document Brotzky measured is 84 KB compressed. It reached his laptop in Vancouver from a nearby Cloudflare edge with a time to first byte of around 50 to 65ms. About 30 KB of that is real markup (sidebar, greeting, composer) plus the styles to paint it, and after hydration the page has 548 DOM nodes. Inline scripts in the head apply the saved theme before first paint and timestamp two moments for every user: when the HTML starts executing (__oai_SSR_HTML) and the first animation frame after it (__oai_SSR_TTI). Both go into real user monitoring.
A cold logged-out load pulls over a hundred JavaScript chunks, but the document modulepreloads only 14: the entry, the React vendor chunks and the modules for the matched route. Everything else waits for the composer flag. Most chunk filenames are anonymous hashes. The core of the chat ships under a readable name, conversation-small, while code block rendering and chain-of-thought views load in their own chunks only when an answer needs them. CSS is split the same way, with files like code-block.css and cot-message.css loading alongside their features.
The composer itself is a full ProseMirror editor. The server renders a static placeholder that looks identical, and ProseMirror hydrates over it once the bundle arrives. The page loads no webfont for UI text and uses each platform’s system font stack. The only font files it requests are a small semibold woff2 for brand moments and the KaTeX math fonts, which download only when an answer contains math.
The server also decides what the client should warm up. Route loader data streams in parallel with the HTML and carries per-user flags such as shouldPrefetchModels and shouldPrefetchHistory. Logged-out visitors never wait on an auth check. A parallel anonymous backend (/backend-anon/me, /backend-anon/models, /backend-anon/conversation) gives each one a real user ID, so rate limits and experiments work without an account. All chunks, stylesheets and fonts come from chatgpt.com/cdn/assets with 30-day cache headers, so no third-party origin adds a DNS lookup or a TLS handshake. And there is no service worker. Linear precaches about 1,200 assets so its app can boot offline. ChatGPT caches nothing beyond standard HTTP, which Brotzky reads as deliberate: an offline chat app is useless without the model on the other end.
377 KB of decisions
The largest item in the document is a script tag holding 377 KB of JSON, the client bootstrap. It carries auth status, locale, region and a snapshot of the experimentation state, evaluated on the server for each request against the visitor’s anonymous ID and region. On the day Brotzky checked, that came to 556 feature gates, 144 dynamic configs and 192 experiment layers, all inlined into every visitor’s page.
Because the flags are already resolved when React hydrates, the client makes no call to a flag service on boot. That avoids a choice between blocking render on the network and rendering defaults that flicker when the real values arrive. The gate names are hashed: the payload shows 3479398748, not a readable label. Brotzky connects this to the leakers who mine ChatGPT’s frontend for unreleased features, and admits he is one of them for this article. Event logging goes through chatgpt.com/ces/v1/ on OpenAI’s own domain instead of Statsig’s endpoints, so visitors running ad blockers don’t drop out of the experiment data.
Some flags control the loading strategy itself. Next to the composer flag sit promoteCss and stripModulepreloadImports, server-side switches that change how the document loads its own CSS and JavaScript. OpenAI ships both variants and compares real users’ load times. A quote from OpenAI on Statsig’s site mentions more than 600 features launched behind its flags. In September 2025 OpenAI bought Statsig for $1.1 billion and made its founder, Vijaye Raji, CTO of Applications, in charge of engineering for ChatGPT and Codex.
Where the effort goes
Each streamed token is a fragment of markdown, and the app re-parses and re-renders the growing message as tokens arrive. Mid-stream the markdown is usually incomplete: a code fence still open, a table with only some of its rows, a bold marker without its closing pair. Code blocks in answers are full CodeMirror editors (.cm-editor shows up in the DOM), the same library behind canvas. OpenAI sponsors Marijn Haverbeke, who created both CodeMirror and ProseMirror. KaTeX renders every formula twice, once visibly and once as a hidden MathML tree, so screen readers can read it aloud and users can copy it as real math.
Around the answer, everything is standard. Menus, selects, toasts and popovers are Radix primitives, which Linear and Conductor also use. Theming in Tailwind runs on token-* classes backed by CSS variables, so light mode, dark mode and chat themes swap values without re-rendering components. The message list isn’t virtualized, and every message in a conversation stays in the DOM. Brotzky’s explanation is that most conversations are short, browser find-in-page has to work for everyone, and virtualization adds complexity and accessibility costs. For an app this size, that is probably the boldest thing on the list: a performance technique it chose not to use.
The bouncer behind the open door
Anonymous access to a frontier model also makes the site a target for scripts, and every abusive request costs GPU time. Before a visitor sends anything, Cloudflare runs a proof-of-work challenge (the cdn-cgi/challenge-platform requests) to make the client spend CPU proving it is a real browser. OpenAI’s own anti-abuse system, Sentinel, starts in a sandboxed iframe (sentinel/frame.html, with a separately versioned sentinel/sdk.js) so it can fingerprint the client separately from the app. Researchers who decrypted Cloudflare’s challenge on chatgpt.com found it checking dozens of properties of the running page, including the app’s React Router state.
The app also calls /backend-anon/sentinel/chat-requirements/prepare and finalize while the user is still typing, so the abuse checks are done before they hit enter. Then a quick prepare call confirms the requirements still hold, and the message goes out as a POST to /backend-anon/f/conversation. The response is a text/event-stream, server-sent events over fetch, the same pattern ChatGPT has used since 2022. The request body includes a client_contextual_info object with viewport size, pixel ratio, dark mode state and seconds since page load.
Brotzky compares this with claude.ai, which shows a login wall and onboarding before the first prompt. It is a client-side rendered single-page app served from a CDN, which works fine when every user is logged in. OpenAI instead runs bot defense, challenge flows, anonymous rate limiting and a whole parallel API so that a stranger’s first prompt meets no friction, and it has started offsetting the cost of those free answers with ads. “You can read each company’s strategy straight out of their network tab,” Brotzky writes.
Up next
OpenAI’s agents were poking at Hugging Face back in May
Per Reuters, two Hugging Face accounts hijacked by OpenAI’s agents were sending oddly formatted files to the site’s servers on May 13, two months before the July breach. An independent researcher spotted it last week.
Related stories
- OpenAI’s Habitat grew from a Python library to 22M req/sec
- A full LibreOffice ships inside the ChatGPT desktop app
- 8Braid pins an exact margin inside OpenAI’s fluid proof
- GPT-6 Astra designed a Codex device in under a week
- OpenAI’s Pando cut monorepo push latency nearly 5x
- OpenAI’s monitors caught a replayed breach 30+ hours early
Comments
No comments yet. Be the first.
Join the conversation
Sign in to leave a comment. It appears after moderation.

