Migrating our site from a .com to a .co.uk domain meant that right from the beginning, we knew some of our incoming traffic would hit bad URLs, no matter how good we were. So what do you show then? So many companies treat the 404 like a tombstone - we decided the page guaranteed to see traffic you weren't expecting during a migration is the perfect place for a small browser game. So here's why we did it, how we did it, and the cost of doing it.
The overlooked bit about domain migration has always been that the 301 redirect map is a best guess at best, not a HOLY GRAIL. We did try to craft an exhaustive map of what we thought would hit it, and even some known URLs failed to make it onto the map and instead had this; nearly every valid domain landed exactly as expected, with the exception of...
PATCH requests, or update to my site!).?stage=infant&loc=old-centreSo we all know what the traditional 404 page looks like. Nothing to it but to "exit". It’s barely any SEO points and just leaves users frustrated. You've already gained and spent SEO equity driving them there; it seems a waste to throw it away like that.
So our new mantra was simple: 'Hope for bad redirects and reward any visit to them'.
My first idea was that we build a tiny game, but immediately following it was the thought that on an error page, this has to have as low a load time as possible. If a user visits from an unreliable 3G connection, they could spend 500ms watching a game page that would normally appear immediately. If we tried to build this in with Phaser or another one of these popular but large games, users who already expected something bad from us would rage-quit and never return. So we had to come up with a game with strict constraints for load time:
requestAnimationFrame is perfect on the 2D canvas, as well as keeping it all nice, battery-efficient, and performant.A simple arcade game wouldn't really align with our site, so we decided to play on the site's child education themes. We needed to make people think, with words flying around and having meanings beyond the literal; we displayed PORT, NORTH, SOUTH, NEXT, PEAK with word-based prompts requiring people to select the correct arrow to go to either the port direction or the 'meaning' required. So you got little words like "PORT" appearing when left-right arrows were displayed. This effectively is a reversed micro-Stroop test; it requires you to interpret, rather than match.
There's a closure (we do use no libraries for anything):
type GameState = 'START' | 'PLAYING' | 'GAMEOVER' | 'HELP';
let state: GameState = 'START';
let score = 0;
let speed = 2.5;
let obstacles: Obstacle[] = [];
Hit confirmation, if signal is in central hit zone. Hit early or late and signals are missed, as is hitting the wrong arrow and you miss the next shot: The hit-confirmation logic is actually essential to the feel of the game:
function handleGameInput(code) {
if (state !== 'PLAYING') return;
if (!['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(code)) return;
const activeCue = obstacles.find((o) => {
const cx = o.x + o.width / 2;
return cx > HIT_ZONE_START && cx < HIT_ZONE_END; // in the brain
});
if (activeCue && activeCue.targetKeys.includes(code)) {
score += 10; // 10 "BOLTS" per intercept
speed += 0.2; // <-- the difficulty curve, one line
obstacles = [];
spawnCue();
} else {
triggerGameOver(); // wrong key, or no signal in the zone
}
}
Acceleration (just line speed) is: speed += 0.2 (line moves faster with correct signals) Correctly timed signals allow upcoming ones to pass by quickly, the better your combo, the better you play. It's the combo that functions as the timer.
Mobile UI. Touch keyboard unusable with current browser features; therefore, four directional buttons were implemented as DOM elements with data-dir attribute attached. They are triggered using pointerdown (not click due to mobile lagging 300ms).
The handleGameInput method accommodates both key code and button direction depending on input:
container.querySelectorAll('[data-dir]').forEach((btn) => {
btn.addEventListener('pointerdown', (e) => {
e.preventDefault(); // no double-fire, no scroll
handleGameInput(btn.dataset.dir); // 'ArrowLeft', etc.
});
});
Canvas declared with touch-none style to stop stray swipes, leading you out of the game.
Escape Hatch: A rule I'm taking with me into my future gamified 404 experiences: never trap your users. There is always the main page header visible above the game, and just below there's a "Back to home" button, and a "Explore our programmes" link. It's an invitation to play, not a sentence, so the user is always able to exit: Interestingly, this strategy actually leads to users staying longer.
Desired "beat my score" loop, but there was no chance for backend/authentication for our 404: The solution was to make the score itself contain its history. At game over, a Wordle-type score chart is compiled and provided to the user via the share function in the OS or into the clipboard if not available; it becomes an object for sharing containing the same data: A Wordle-like grid(emojis/bits) that contains the actual score, call to action, and telemetry.
async function handleShare(score) {
const text = buildShareGrid(score) + // ⚡ grid + BOLTS: 120
`\nCan you beat it? https://shichida.co.uk/404brain\n#404brain`;
if (navigator.share) {
await navigator.share({ title: '404brain', text });
} else if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
setShareMsg('Score copied — paste it anywhere!');
}
}
Because our database (gtag/dataLayer) didn't store information beyond a few metric events (game started, game over), any share will contain only its share-related data and a stable link pointing to the indexable home page.
The value of the page has manifested from its usage. We used to suffer from a 4s bounce with our default 404 – click a link, sigh, abandon. Session behavior under the redirect was significantly better; post-launch sessions spent between 30-90 seconds interacting on the page, a notable portion of which turned to consumption after an additional 30-90 seconds.
Response outside of the development teams was far better than anticipated. The independent UX & Copywriting Blog Keep It Simple Copywriting by Kate Ingham-Smith featured it in their Best 404 Pages roundup and scored it 5/5 for Coolness and 5/5 for Creativity, which is clear proof that they understood and enjoyed the message.
The full implementation can be explored at shichida.co.uk/404, and it has its own permanently fixed address of shichida.co.uk/404brain. There is no hidden magic-just look at the source.
navigator.share API with a fallback clipboard mechanism and hashtag), removing the need for a server, authorization infrastructure or personal data protection for a growth loop.We made a small investment (a few days max) into something we didn't expect to ever get seen that turned out to be by far the most trafficked page domain-wide. As Kate wrote on her blog:
Games are always a good addition to a 404 page as they not only make people stick around, but help them remember your brand.