Plate № 30 · Routing · antifragile redirects

A pattern from the gf.cx specimen book

Principle · ops baseline

"Remove the coupling, not the component." A capability shouldn't depend on any single host being up — so move the host off the page and let a resolver find a live one at request time.

The problem

Some external resources have no stable home. A shadow library, a censored mirror, a rotating CDN — the canonical host keeps dying: takedowns pull one domain, the operators spin up another, a third comes and goes for days at a time. The moment you hard-code https://some-host.org/… into a page, you've written a link that is one takedown away from a dead end — and you won't know it broke until a reader hits it. Editing every page each time a mirror rotates doesn't scale, and it couples your content to a host you don't control.

The shape

Don't put the host on the page at all. The page links to a small resolver on your own domain — a request-time function — and passes only the intent (a query, a format). At request time the resolver probes a preference-ordered mirror set and 302-redirects to the first host that answers. The page carries no host; it self-heals as mirrors rotate. Widen the mirror set once and every page that links to the resolver inherits the new host — the system gets stronger as you add mirrors, which is what makes it antifragile rather than merely robust.

The reference implementation is growth.gf.cx/functions/go/annas.ts — a Cloudflare Pages Function at route /go/annas?q=<query>&ext=<pdf|epub>. It resolves Anna's Archive, whose domains (annas-archive.org / .se / .pk / .li / .gs / .gl) rotate constantly under takedown pressure. A page links /go/annas?q=Title+Author&ext=epub and never names a mirror.

The probe

A host is "alive" if it answers at all. The probe is a HEAD request with a short AbortController timeout: any HTTP response — even a 403 or 405 — means the host resolves and is serving, so it counts as alive. Only a network error, DNS failure, or timeout counts as dead. This is deliberately generous: a mirror that 403s a bare HEAD still serves the search page to a real browser, so treating it as dead would discard a working host.

const MIRRORS = ["pk", "li", "gs", "se", "org", "gl"];
const DEFAULT_TLD = "pk";        // last-resort target if every probe fails
const PROBE_TIMEOUT_MS = 2500;

async function isAlive(tld: string): Promise<boolean> {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
  try {
    await fetch(`https://annas-archive.${tld}/`, {
      method: "HEAD", signal: ctrl.signal, redirect: "manual",
    });
    return true;                 // any response = alive
  } catch {
    return false;                // network error / DNS / timeout = dead
  } finally {
    clearTimeout(timer);
  }
}

Mirrors are probed in preference order — currently-live hosts first (pk, li, gs), the official ones (se, org) as fallback — and DEFAULT_TLD is the last-resort target if every probe fails, so the resolver always produces a destination rather than an error.

Caching & the no-store redirect

Probing six hosts on every hit would be slow, so the resolved host is cached ~10 minutes per colo via the Cache API (caches.default with a synthetic key), written with context.waitUntil so it never blocks the response. The one subtlety: the 302 itself is sent with cache-control: no-store. That means the client always comes back through the resolver and can never pin a host that later dies — only the resolver's internal choice is cached, and only briefly.

const cache = caches.default;
const cacheKey = new Request(MIRROR_CACHE_KEY);
let tld = "";
const cached = await cache.match(cacheKey);
if (cached) tld = (await cached.text()).trim();
if (!MIRRORS.includes(tld)) {
  tld = await resolveMirror();   // probe in preference order
  const store = new Response(tld, {
    headers: { "cache-control": `max-age=${MIRROR_TTL_S}` },  // ~600s
  });
  waitUntil(cache.put(cacheKey, store));
}

// no-store so the client always re-resolves through us (never pins a host):
return new Response(null, {
  status: 302,
  headers: { location: target, "cache-control": "no-store" },
});

Safe by construction

A request-time redirector is an open-redirect waiting to happen if you let the input choose the host. This one can't:

Companion lesson · search by title, not ISBN

A practical footnote for shadow-library search specifically: search by title + author, not ISBN. New books aren't ISBN-indexed for months, and per-format results can be empty in the early window — that's expected, not a broken link. The title search self-populates as uploads land, so a title query degrades gracefully where an ISBN query would look dead.

Where it applies

When it breaks

Reference

Reference impl
growth.gf.cx/functions/go/annas.ts — a Cloudflare Pages Function at /go/annas?q=<query>&ext=<pdf|epub>. Probes MIRRORS = ["pk","li","gs","se","org","gl"] in order (HEAD + 2.5s timeout), caches the live host ~10 min per colo via the Cache API, and 302s with cache-control: no-store. Shipped & live.
Principle
The ops baseline's remove the coupling, not the component — antifragility by decoupling from any single dependency — turned on an outbound link instead of an unattended job. Kin to the antifragile contact form (same principle, on an inbound form) and the memorable-URL canonical redirect (same mechanism, opposite intent — a permanent home, not a rotating one).
Rule
The page carries the intent; a resolver on your own domain carries the mutable host. Widen the mirror set and every linking page self-heals — stronger as you add mirrors.