Routing & Custom Domains

This page documents how static.bio resolves incoming requests to a profile, with a focus on custom domain routing.

Design goals:

  • Public profiles are served as static HTML + CSS (no JS).
  • Custom domains behave like first-class origins (no "redirect to static.bio" by default).
  • Routing is auditable and predictable.

Request types

There are two primary public entrypoints:

  • Username route
    GET https://static.bio/<username>
  • Custom domain route
    GET https://<custom-domain>/ (and optionally other paths, depending on implementation)

Both routes resolve to the same profile render pipeline; only the lookup key differs.

Lookup strategy

Username lookup

  • Extract <username> from the path.
  • Query profile by username (case rules are an implementation decision; keep it consistent).
  • If found, load:
    • profile row
    • enabled links (ordered)
  • Render with the selected theme.

Domain lookup

  • Read the Host header (or equivalent in your platform/router).
  • Normalize:
    • strip port
    • lower-case
    • optionally remove leading www. (choose and document one canonical approach)

Then query a domains mapping table:

  • domain -> profileId

Load the profile and links by profileId, then render as usual.

Rewrite vs redirect (critical choice)

Custom domains should use a rewrite model, not a redirect model.

  • Request stays on https://<custom-domain>/...
  • HTML and assets are served from the custom domain origin (which points to static.bio)
  • The user's domain remains canonical

Benefits:

  • Better trust and branding for the user
  • Cleaner sharing (no visible hop)
  • Aligns with "static site" mental model
  • Request to custom domain redirects to https://static.bio/<username>
  • The custom domain becomes a thin alias

This is simpler, but undermines the "custom domain as canonical" value proposition.

Path handling

Most profiles should treat the profile as a single-page site.

Recommended behavior:

  • / renders the profile
  • Any other path either:
    • returns 404, or
    • rewrites to / (single-page behavior) only if you have a strong reason

Be explicit: avoid surprising behavior where arbitrary paths resolve without clear intent.

Asset URLs under custom domains

To keep the "no third-party resources" guarantee meaningful across custom domains:

  • Use relative URLs or same-origin absolute URLs for base/theme CSS.
    • Preferred: href="/css/base.<hash>.css" and href="/css/theme-...<hash>.css"
  • Avatar URLs may be external (user-provided) unless you proxy/host them.
    • If you want stricter guarantees, host avatars on static.bio and rewrite to same-origin.

If you do allow external avatars, document it clearly in Privacy/Security and Audit pages.

Canonicalization rules

To avoid ambiguous routing, define a canonical approach:

  • Either enforce www. as canonical, or strip it
  • Either allow both apex + www pointing to same profile, or reject duplicates

Common approach:

  • store and match the exact configured domain string
  • optionally support automatic www. aliasing as an explicit feature

Avoid "magic" unless it's deterministic and documented.

Data model (typical)

A minimal schema is:

  • profiles: user/profile data
  • links: ordered link set
  • domains: custom domain mappings

Example fields:

  • domains.domain (unique)
  • domains.profileId (FK)
  • optional verification fields (challenge token, verifiedAt)

Verification & ownership

Custom domains must be verified before activation. Typical flow:

  1. User enters domain in dashboard.
  2. System generates a verification token.
  3. User proves control via:
    • DNS TXT record, or
    • HTTP challenge at a well-known path

After verification, the domain is marked active and begins routing to that profile.

Even if you haven't implemented verification yet, document the intended mechanism. Developers expect this.

Failure modes

Unknown username

  • Return a static 404 page (rendered with a default theme) without JS.

Unknown domain

  • Return a static 404 (or a generic landing page), but never leak internal IDs.

Domain conflicts

  • Reject config if a domain is already claimed.
  • Do not allow two profiles to resolve from the same domain.

Partial setup

  • If a user has configured a domain but not verified it:
    • show a dashboard status
    • do not activate routing

Known tradeoffs

  • Rewrite requires correct hosting/CDN config. It's slightly more work than redirect, but it delivers the real value of custom domains.
  • External avatars complicate "same-origin" purity. If avatars remain external, the "no third-party resources" claim should exclude user-provided images or you should proxy them.
  • Domain normalization is opinionated. Pick rules and enforce them to avoid edge-case bugs.

Contract tests (routing-level)

These are invariants worth regression-testing:

  • Username route resolves profile by path segment and renders deterministically.
  • Custom domain route resolves profile by Host header and renders deterministically.
  • Custom domain requests do not redirect to static.bio (unless explicitly configured).
  • CSS asset URLs are same-origin (prefer relative paths).
  • Unknown username/domain returns a static 404 document with no JS.