How I Turned a 404 Error Into a Zero-Dependency Game to Save a Domain Migration

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.

1. The Migrating Cliff

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…

  • Legacy print citations – we had a parent’s magazine reference class names back in print, dated to 2019 (the URLs had long since been restructured, and print magazines can’t do PATCH requests, or update to my site!).
  • Mis-typed and abbreviated paths – The 2019 magazine wrote ‘programme’ when the route was indeed ‘programmes’ – that was also missed. We have customers who typoed their address too, or deliberately shorten links for business cards and flyers.
  • Links from other sites with stale parameters – We had affiliate links that sent traffic with old filtering parameters in the URL; e.g. ?stage=infant&loc=old-centre
  • The long tail – we had old preview URLs, cached Google links (from 2016), or a particularly old Pinterest link. Each one bounced.

So 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’.

2. The Restriction- The Error Page had to have zero-bloat

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:

  • Must load in less than 100 ms.
  • No 3rd-party game dependencies. This eliminated every single engine on the market and pushed us to do the bare minimum:
  • Using only raw 2D canvas (800×400), and not WebGL, to save on initial setup and keep draw calls clean.
  • requestAnimationFrame is perfect on the 2D canvas, as well as keeping it all nice, battery-efficient, and performant.
  • A simple, pure, closures-scoped state machine (the game logic). NO react or React extensions in the rendering for anything. All rendering data (score and object states, etc.) were passed around simply as variables, and all the data was immutable so we could save on unnecessary computations and calls in the effect lifecycle; just keep drawing what state we are currently in.
  • It was styled purely with CSS3, including the surrounding shell of the page, mobile-friendly touch controls, and a small fake-terminal style border-this just leaves you with a zero-weight component.

3. Engineering a Cognitive Task Game on a 404 (or as we call it: ‘404Brain’)

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.

The State machine

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[] = [];

Input, with hit-zone filtering

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 and Mobile UX

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.

4. Growth Loop: Sharing. No Backend Needed

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/404brainn#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.

5. Receipt and Validation

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.

6. A Note to Engineers and Founders

  1. All redirects have a leak, and they must be addressed. Invest attention in the page which handles the 404 case: make it into a guaranteed acquisition channel – it is, in fact, one; it bears some share of your circulation budget and needs to feel like an exit sequence for your user to begin with.
  2. Constraint is a feature. The game needed to run in <100ms. Writing in canvas and closure instead of a game engine forced that constraint on us-this is both the most valuable and terrible constraint a programmer ever received: no guessable runtimes to account for, no assets to bundle with the download and no parts that can go wrong remotely.
  3. You can implement a growth loop without a backend. A portable and self-contained share artifact is what’s responsible for the virality budget (the 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.
  4. Always gamify the offer, not the exit. Don’t hide the exit criteria or remove easy access to the main navigation. Letting the user leave at their leisure means they can focus on having a pleasant time playing the game, and that’s a far better retention mechanic.
  5. Dead ends are opportunities to present a better offer. Users don’t anticipate a rewarding or insightful experience through the error page. Any value that can be derived from the session at that point is pure upside, as it falls outside the domain that most users expect to be addressed.

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.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.