X published an engineering post on 4 November describing the rebuild of x.com, and the first number in it does most of the work. On a throttled connection, the old web app took more than 22 seconds before the first post appeared. The new one does it in 2.7.

The post, by principal engineer Yagiz Nizipli, explains why. x.com used to run three servers: rweb rendered the app in the browser, rweb SSR rendered it for search crawlers, and Jetfuel SSR served a separate server-rendered site for indexed logged-out pages — events, Spaces, Communities, Trends, news articles. Each did its job well. The trouble was that a single URL could be built three different ways, every SEO fix had to land in three codebases, and logged-out visitors — the humans — got the slowest path: application shell, then JavaScript, then a GraphQL request, then a render. Crawlers got finished HTML. People did not.

The new app serves all three audiences from one server: Vite with Rolldown, React with streaming server rendering and Suspense, Fastify as the request pipeline, Node.js 26 across worker threads via Platformatic Watt, Relay with GraphQL, and an in-house component library called XDS. TypeScript is checked by `tsrs`, a Rust port of the TypeScript compiler.

How one server decides

People get streamed HTML — shell immediately, each part of the page as its data arrives, and every GraphQL response the server received carried once in the page so the browser can replay it without asking again. Verified search crawlers get a complete document that never hydrates: no client bundle, no router state, no placeholders. Verification is done against the crawler's IP address at the edge, not its User-Agent header. X gives two reasons: a header check would have buffered Lighthouse runs into the expensive path too, and it would have let anyone request that path by lying in a header. Link-preview bots — Slack, iMessage, Discord, WhatsApp and the rest — get the title, description and card image, with the page body skipped entirely. Anything else that claims to be a crawler gets exactly what a logged-out visitor gets.

The numbers

A lab comparison recorded on 3 November, signed into the same account, on Lighthouse's Slow 4G profile with an empty cache and service workers off: the new app shows the page at 2.4 seconds and the first post at 2.7; rweb shows its splash at 1.0 and holds it until the first post at 23.9. Across pages, signed-in profiles and posts arrive 8 to 13 times sooner on a slow connection. Home gains least — the new app renders the signed-in timeline only in the browser — at 14.1 seconds against 24.6. On JavaScript downloaded before the first post, rweb moves 3.0 to 3.6 MB compressed; the new app moves none for a profile or post, because the post is already in the HTML, and 1.9 MB on Home. Per full page load the new app downloads 1.3 to 2.3 MB against rweb's 4.1 to 4.8 MB.

The infrastructure effect is the real prize. The logged-out rweb fleet halved from 1,400 to 700 instances per data center; rweb SSR halved from 2,400 to 1,200 and was then shut down. Crawler traffic moved first — Jetfuel SSR retired on 1 August, the new app took the whole crawler load in early September and rweb SSR went with it, deleting almost 36,000 lines from rweb along with its deploy and build. Jetfuel's rendering and image services were removed from their repository in mid-September.

Because search engines notice everything, the team treated the old servers as specifications and wrote a parity script that requested the same path from both as a verified Googlebot, comparing status code and redirect, title, description, canonical, robots tags, Open Graph and X card tags, the number of language alternates and the types of structured data. Googlebot reads only the first 2 MB of a response, and long threads ran 2.2 to 3.5 MB — the replies past the cut were lost along with the structured data pointing at them. Three changes fixed it: a build-time icon sprite instead of seven inline action icons per post (saving 165 KB on @elonmusk's profile and 340 KB on a long thread), de-duplicated photo structured data (450 KB on a photo-heavy thread) and stripping output that only matters after JavaScript runs (54 KB).

Five engineers, a lot of agent work

The first version was built in under six months by five engineers working with AI coding agents; more than 20 people have committed since the end of September. More interesting than the speed is how it was made safe. An `AGENTS.md` at the repository root sets the rules — named exports, no barrel files, validate external data at trust boundaries, nothing committed until lint, typecheck and the relevant tests pass. Over 25 recurring workflows live next to the code as skills. Custom oxlint plugins enforce Relay usage, Tailwind classes, tests and translations, including an "anti-slop" plugin that rejects patterns agents tend to produce: chained type assertions, parameters typed `unknown`, conditional spreads of empty objects. A pre-commit hook formats, lints and type-checks every change.

The migration suited agents precisely because the old servers were working specifications. An agent could read how rweb SSR handled a URL, port the behaviour and pin it with a test, and a reviewer could check the result against the original. Nizipli is candid about the shift for the humans: less time writing code, more time deciding what the system should do and then checking in a real browser that it does.

Rollout runs through edge routing and a feature switch, one country at a time, with any group revertible without a deploy. rweb stays live until everyone has moved — which is the direct answer to the objection Nizipli quotes himself, Joel Spolsky's warning that big rewrites stall because the old product keeps moving. Here it kept moving, and the agents shortened the race.