Skip to content
2025
  • Next.js 14
  • TypeScript
  • CSS Modules
  • Middleware

Dual-Domain Identity System

One codebase, two completely different brand identities — served at runtime by domain, with zero build-time duplication.

FORMÆTRIX (a literary studio) and ryanpyles.com (a personal archive) needed to coexist in a single Next.js repository without bleeding into each other visually, semantically, or structurally. Maintaining two separate repositories for what was essentially one interconnected system would have meant duplicating layout infrastructure, navigation logic, SEO primitives, and the entire design token architecture — then keeping them synchronized indefinitely. The real constraint was that the two identities are aesthetically opposite: one is a dark, high-contrast studio brand with orange accents; the other is a manuscript-paper archive with terracotta and brass. They couldn't share a palette.

The solution was domain-scoped CSS custom property remapping layered over a shared token system. Every shared component reads color through semantic names (--color-black, --color-white, --color-accent) — and a single [data-domain] attribute on the root layout element redefines what those names resolve to per domain.

  • Middleware domain detection: edge-level request inspection reads the Host header and sets a response header, which the root layout uses to select the domain branch. No client-side JS, no cookie dependency.
  • CSS custom property inversion: under [data-domain='ryan'], --color-black becomes the paper background (#F5F1EA) and --color-white becomes the ink foreground (#1A1A1A). The semantic names invert in meaning but every downstream component still just reads var(--color-white) for foreground text — no per-component changes needed.
  • Route isolation: formaetrix pages live under /formaetrix/* with their own layout. Ryan pages are root-level. Both share the same SiteLayout component, Navigation, Footer, Section, and ProjectCard — differentiated entirely by the data-domain cascade.
  • Content separation: ryanBooks and formaetrixBooks are typed and colocated in content/books/ but exported separately. No cross-contamination at the data layer.

The critical implementation detail is that CSS custom property inheritance respects the DOM tree — [data-domain='ryan'] on the <html> element means every descendant's var(--color-black) resolves to #F5F1EA, even inside a shared component that was written for the formaetrix dark register. This means zero conditional rendering in shared components; they simply inherit the correct values. The only place domain logic appears in component code is in components that need domain-exclusive behavior (like BlobNav, which only renders on formaetrix). The performance profile is clean: no client-side domain detection, no hydration mismatch risk, no extra CSS bundles. Both domains share the same stylesheet and same JS bundle.

A single deployable unit serving two distinct brand identities. Adding a third domain (hypothetically) would require one new [data-domain] block in globals.css and a middleware Host match — no new components, no new routes, no new build configuration.

  • Zero shared components duplicated
  • One stylesheet, one JS bundle
  • Edge-level domain detection with no client JS
  • Two fully independent visual identities from one token system