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
Hostheader (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.
Rewrite (recommended)
- 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
Redirect (not recommended as default)
- 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"andhref="/css/theme-...<hash>.css"
- Preferred:
- 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 datalinks: ordered link setdomains: 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:
- User enters domain in dashboard.
- System generates a verification token.
- 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
Hostheader 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.