Splitting the site in two took forty-four minutes and created an inescapable loop
Eleven commits between 23:13 and 23:57. The whole thing shipped switched off, and the rollback plan was four words long.
For most of this product's life, the marketing site and the application lived at the same address. The homepage sold the thing, and if you signed in, the same domain served you the thing.
That is the simplest arrangement and it is wrong for one specific reason: search engines. A marketing site wants to be crawled comprehensively. An application wants to be crawled never. When they share an origin, every rule you write has to be conditional, every robots directive is a compromise, and the crawler spends its budget on pages that require a login to mean anything.
Splitting them happened on the night of 2026-07-17, in eleven commits between 23:13 and 23:57.
Forty-four minutes. Two of them share the minute 23:45, and the last two share a timestamp to the second. Then nothing until midday, when two more landed in the same minute, a third that afternoon, and two more late that night.
It shipped switched off
The second commit of the run creates a host router: 178 lines of routing logic and 189 lines of tests, in a three-file commit with zero deletions.
The design note that matters is one sentence: the router is inert unless two environment variables are set, and unknown hosts pass straight through. The entire split, every redirect, every per-host rule, shipped dark. Nothing changed for anybody until somebody set two variables.
The rollback plan in the runbook is four words: delete two environment variables.
I want to sit on that for a second, because it is the whole reason a change of this blast radius could be done in forty-four minutes by one person at midnight. Splitting origins touches routing, authentication, cookies, analytics, the sitemap, canonical URLs, the Chrome extension and every internal link. Any of those could be wrong. What makes it safe is not that it was done carefully, although it was. It is that the mechanism to undo all of it at once is a checkbox in a dashboard, and it does not require a deploy, a revert, or a person who understands the change.
If a large change cannot be turned off by someone who does not understand it, it is not ready, however good the tests are.
What came out
The split turned out to be mostly a deletion exercise, and what got deleted is a catalogue of things nobody had looked at in months.
One commit removes the dead anonymous and demo code paths and takes 285 lines with it. Inside were fake dashboard statistics, a made-up pipeline figure and a rank, rendered to signed-out visitors. Those became neutral fallbacks for the case where a query genuinely fails. Fake task rows, feed rows, quest rows and leaderboard rows went too.
Another retires the inline login bar, deleting four functions by name and 136 lines. Along with them went a fake persona that had been showing signed-out visitors a fictional user at level fourteen, with an email address at a company that does not exist.
Analytics tags came out of the application shell entirely, on the grounds that product usage by signed-in customers is not marketing traffic and should not be sent to a marketing analytics tool.
The per-host search rules commit contains my favourite line of the night. Removing a blocking directive from the marketing side, it notes, also dissolves a bot-precedence bug and a contradiction between the sitemap and the robots file. One deletion resolving two unrelated bugs that nobody had set out to fix, because both were caused by the complexity that only existed to make one origin serve two purposes.
That is the real argument for splitting. Not the crawl budget. The fact that a whole category of conditional weirdness stops being expressible.
The loop
Three more commits followed the next day, two of them review rounds, and the best finding is a genuinely novel failure that the split itself created.
The product had a mobile interstitial: visit on a phone, get bounced to a page suggesting the desktop experience, with a dismiss option. Dismissing set a flag in a cookie so it would not happen again.
After the split, the interstitial lived on the application origin. The dismiss flag was therefore stored against the application origin's cookie. But the bounce was still firing for phone traffic arriving at marketing pages, on the marketing origin, which cannot read that cookie.
So: a phone visitor lands on a public page, gets bounced, dismisses it, gets returned, and is bounced again. Forever. The dismissal was being recorded somewhere the check could never look.
Nothing about either piece of code was wrong. The interstitial worked. The dismissal worked. They stopped working the instant they were on opposite sides of an origin boundary, because a cookie's scope is exactly the thing an origin split changes and exactly the thing nobody thinks about when splitting.
The second review round found a booby trap of a different kind. The environment template file had entries like VARIABLE= with nothing after them. The library that reads it exports those as empty strings rather than leaving them unset, so code that fell back to a default when the variable was absent instead received an empty string, tried to convert it to a number, and crashed on startup. Copying the example file, the first thing any new contributor does, produced a server that would not boot, and it also silently prevented two background workers from starting.
The fix rewrote 123 lines of that file to comment out every optional entry, leaving exactly two uncommented, both genuinely required.
That review also found two tests that were passing for no reason. One asserted a redirect went somewhere, using a condition that was true regardless of the answer. The other checked against a value that had been removed. Both had been green the entire time and testing nothing, which is worse than absent, because absent tests do not provide confidence.
The runbook for the cutover was written that night and then corrected twice over the following days. The third version is shorter than the second, because it had to admit that a set of steps it described could not be performed yet: the mail credentials it assumed were in place had never been added.
A document that shrinks when it meets reality is a document doing its job.
— DealArena Team