openai
ChatGPT грузится вокруг вопроса «можно ли уже печатать»
Promtime
openaiВ конфиге загрузки chatgpt.com есть серверный флаг с именем deferStartupImportsUntilComposerTTFI: отложить стартовые импорты до момента, когда в поле ввода уже можно печатать. Нашёл его Деннис Бротцки, который несколько дней разбирал HTML, бандлы и сетевые запросы chatgpt.com для Performance.
Коротко
- Разлогиненный документ приходит одним ответом на 84 КБ, внутри около 30 КБ настоящей разметки, первый байт доходил за 50–65 мс, а после гидратации на странице остаётся 548 узлов DOM.
- В том же документе лежит скрипт на 377 КБ JSON: 556 фич-гейтов, 144 динамических конфига и 192 слоя экспериментов, посчитанных на сервере под регион и анонимный идентификатор гостя.
- За вход без логина платит невидимый слой защиты: proof-of-work от Cloudflare и антифрод Sentinel в песочнице отрабатывают заранее, пока вы ещё печатаете первое сообщение.
Если вы не следили: ChatGPT запустился 30 ноября 2022 года как приложение на Next.js 12 с Pages Router, внутри OpenAI это был research preview с невысокими ожиданиями. На Pages Router сайт прожил около 21 месяца. 27 августа 2024 года Тибор Блахо заметил, что части пользователей отдают сборку на Remix, к 4 сентября она раскаталась широко, а когда в ноябре 2024 года Remix v2 влился в React Router 7, ChatGPT перешёл следом.
В роутере 354 маршрута, и только около 13 относятся к чату
Сегодня в исходнике любой страницы лежит window.__reactRouterContext с ssr: true и isSpaMode: false, то есть React Router 7 в framework mode с потоковым серверным рендером на Vite. В эпоху Remix сервер отдавал почти пустую оболочку и около 7000 строк JSON в window.__remixContext, мутации шли через собственный API мимо actions библиотеки. Сейчас весь разлогиненный интерфейс приезжает настоящим HTML.
В клиентском манифесте 354 маршрута, и только около 13 относятся к самому чату. Остальное занимают маркетинг и лендинги: ($lang).codex.pricing, ($lang).business._index, ($lang).atlas.get-started. Они живут в том же репозитории, роутере и деплое, поэтому переход с рекламной страницы в чат идёт клиентской навигацией, без перезагрузки. Обычно маркетинг выносят на отдельный сайт.
84 КБ на документ, 548 узлов DOM и первый байт за 50–65 мс
Разлогиненный документ приходит одним сжатым ответом на 84 КБ, внутри около 30 КБ реальной разметки: сайдбар, приветствие, поле ввода. Первый байт до ноутбука автора в Ванкувере доходил за 50–65 мс с ближайшего узла Cloudflare, после гидратации на странице 548 узлов DOM.
Самое интересное сидит в head. До того как запрошен хоть один бандл, отрабатывают два инлайновых скрипта: первый читает тему из localStorage и вешает класс на html, чтобы не мигнуть чужой темой, второй ставит метку времени в момент старта HTML и на первом кадре после него и отправляет её в мониторинг реальных пользователей.
Сам документ стримится: сервер сразу отдаёт оболочку, границы Suspense дозаполняются по мере готовности данных. Вместе с ними приезжает персональный план предзагрузки, который сервер диктует клиенту: shouldPrefetchModels, shouldPrefetchHistory, shouldPrefetchStarredConversations.
Загрузка расписана вокруг флага deferStartupImportsUntilComposerTTFI
На холодной разлогиненной загрузке страница тянет больше сотни JavaScript-чанков, но modulepreload в документе указан только для 14 критичных файлов: входного, вендорных чанков React и модулей совпавшего маршрута. Остальное ждёт того самого флага, то есть момента, когда поле ввода становится интерактивным.
Большинство чанков названы анонимными хешами, и только ядро чата едет под именем conversation-small. Рендер блоков кода и разворот цепочки рассуждений подгружаются отдельно, когда этого потребует ответ.
Всё своё: чанки, стили и шрифты лежат на chatgpt.com/cdn/assets за Cloudflare с 30-дневным кешем, лишних DNS-резолвов и TLS-рукопожатий нет. Service worker отсутствует, кеш ограничен обычным HTTP. Веб-шрифта для интерфейса тоже нет, текст рисуется системным стеком, а шрифты KaTeX скачиваются, только если в ответе есть формулы. Стили собраны на Tailwind поверх слоя семантических переменных token-*, а CSS нарезан по маршрутам и компонентам: code-block.css, cot-message.css, global-modals.css.
377 КБ JSON в документе: 556 гейтов, 144 конфига, 192 слоя
В отданном HTML лежит скрипт на 377 КБ JSON: статус авторизации, локаль, регион и снимок всей системы экспериментов, посчитанный на сервере под анонимный идентификатор и регион гостя. В день проверки это были 556 фич-гейтов, 144 динамических конфига и 192 слоя экспериментов.
Обычная интеграция ходит за флагами по сети на старте и выбирает между блокировкой рендера и миганием дефолтов. Здесь запроса нет вовсе, к моменту гидратации React все ответы готовы. Имена гейтов захешированы, вместо читаемого названия видно 3479398748, а события экспериментов уходят на собственный домен chatgpt.com/ces/v1/, мимо блокировщиков рекламы.
В сентябре 2025 года OpenAI купила Statsig за 1,1 миллиарда долларов, а её основатель Виджай Раджи стал CTO of Applications и отвечает за инженерию ChatGPT и Codex. По цитате OpenAI на сайте Statsig, за флагами запущено больше 600 фич.
До первого токена срабатывают proof-of-work Cloudflare и Sentinel в песочнице
Бесплатный анонимный инференс притягивает скрипты, и каждый такой запрос стоит реальных денег на GPU. Поэтому ещё до отправки первого сообщения работают две вещи: proof-of-work от Cloudflare через запросы cdn-cgi/challenge-platform, где клиент тратит немного процессора и доказывает, что он настоящий браузер, и антифрод-система OpenAI под названием Sentinel, которая грузится в изолированном iframe с отдельно версионируемым sdk.js. Anthropic ту же задачу решает иначе: claude.ai закрыт логином и отдаётся клиентским SPA прямо с CDN.
Проверки оплачены заранее. Пара запросов chat-requirements prepare и finalize уходит, пока вы ещё печатаете, и вышибала успевает посмотреть документы, пока вы идёте к двери.
У бэкенда есть параллельная анонимная поверхность: /backend-anon/me, /backend-anon/models, /backend-anon/conversation. Гость получает настоящий идентификатор, чтобы работали лимиты и эксперименты, ответ приезжает SSE-потоком на POST-запрос, а в теле запроса едет client_contextual_info с размером вьюпорта, pixel ratio, состоянием тёмной темы и секундами с загрузки страницы.
Токены прилетают кусками незакрытого маркдауна, и приложение перепарсивает растущее сообщение прямо на месте. Блок кода при этом не тег pre, а полноценный редактор CodeMirror, а каждая формула едет дважды: видимый KaTeX и скрытое дерево MathML, которое читает скринридер.
Всё это внешняя реконструкция: автор не работает в OpenAI, исходников не видел и собрал картину из самого сайта, случайных докладов, твитов и страницы вакансий. Список сообщений при этом не виртуализируют, каждое сообщение остаётся в DOM; объяснение автора сводится к коротким диалогам и поиску по странице, но, на наш взгляд, для длинных чатов размен спорный.
Чем кончится эксперимент с promoteCss
Рядом с deferStartupImportsUntilComposerTTFI лежат promoteCss и stripModulepreloadImports, серверные переключатели того, как документ грузит собственные CSS и JavaScript. Обе версии едут в прод одновременно, а результаты этих замеров нигде не опубликованы, и какой вариант победит, снаружи не видно. Про сам веб-ChatGPT технических материалов от OpenAI автор не нашёл, так что следующую смену архитектуры, как и в августе 2024 года, сначала заметят в бандле.
Комментарии
Пока никто не написал. Будьте первым.
Присоединяйтесь к разговору
Войдите через Google, чтобы оставить комментарий. Имя и аватар подставятся из вашего профиля Google, а комментарий появится после модерации.
Из Google мы используем только имя и аватар. Почту не сохраняем.
