DEV Community

Taranity
Taranity

Posted on

Seven designs, one URL: how I built a site that changes its whole appearance without changing its address

Most sites that offer a theme swap a few colour variables. I wanted something harder to fake: seven genuinely different designs of the same content, switchable in place, with the URL never changing and search engines still seeing exactly one canonical page.

Here is what that took, and the four things I got wrong on the way.

What it does

taranity.com is a static Astro build on Cloudflare Pages. It renders seven complete design trees from the same content: seven typographic systems, seven page skeletons, seven motion grammars. A visitor picks one, a cookie remembers it, and a Pages Function at the edge serves that design's prebuilt HTML at the same canonical URL. Crawlers get the default. There is exactly one URL per piece of content, and the variant trees carry noindex.

Seventy pages, all prerendered. No server rendering, no per-request work beyond reading a cookie and choosing a file.

The one idea that shaped it

Do the branching at the edge, not in the browser.

The obvious approach is to ship every design's CSS and swap a class on the root element. That means every visitor downloads seven designs to use one, and the flash before hydration is unavoidable. Instead each design is a separate prebuilt tree, and a tiny middleware picks the tree. The visitor downloads one design. The address bar never moves. And because the swap happens before any HTML reaches the browser, there is nothing to flash.

The cost is honest and worth naming: the response now varies by cookie, so the caching has to say so. Every response carries Vary: Cookie, and the variant responses are marked private. Getting that wrong would mean serving one visitor's design to another.

Four things I got wrong

My type checking had never actually run. It passed on my machine for months. It could not pass anywhere else. The command ran the type checker before the step that generates the content types, and that generated folder is gitignored, so my machine had a stale copy and a fresh clone had nothing. The first time CI ran on a clean checkout, it went red immediately. If a gate reads something git does not carry, it has not been tested until CI runs it.

A test key does not remove a third party from your tests. My end to end tests use Cloudflare's published dummy Turnstile key, the one documented as always passing. One morning the contact suite dropped from 75 of 75 to 42 of 75 with no code change. The dummy key removes the scoring, not the browser integrity handshake, so the suite was still hostage to Cloudflare's opinion of my machine. Thirty three failures, and not one of them was about my site.

The fix was structural rather than clever: an explicit declaration that the live challenge is unavailable, which makes the form tests run against a stand in value while the widget checks report honestly. Without the declaration a refusal still fails. A banner says in plain words that the widget was not exercised, so a green run can never quietly mean the bot check is broken.

A cleanup block is not a guard. That declaration is passed as a small file, removed when the run finishes. A review caught what I had missed: a run interrupted before the cleanup leaves the file behind, and the next run reads it and opts itself out silently. I had written a comment describing that exact hazard and then guarded only the exit path. Interrupts skip the exit path. It now clears the file on the way in too.

Every dependency advisory had a smaller fix than I reached for. Four advisories, including one rated critical. In every single case the parent package's own declared version range already permitted the patched release, and only the lockfile was pinning something older. No overrides, no major upgrades, one lockfile change each. Worth checking before proposing anything with blast radius.

Where it landed

411 tests across 29 files. Zero dependency advisories at any severity. A strict content security policy with exactly one pinned inline script hash, which survived four dependency upgrades because I re-measured it against each one. An accessibility audit that drives a real browser across every page type in every design in both colour modes: 50 checks, zero violations, with contrast measured from painted pixels rather than computed styles, because a decorative layer over a control will fool a computed style.

The accessibility statement is the part I would point at. It says what is tested, and then it says what is not: no independent audit, no screen reader testing, and that the audit runs on my machine rather than automatically. That last line is there because it used to claim otherwise, and that was not true.

https://taranity.com

Top comments (0)