Architecture

This page describes how static.bio serves public profiles while keeping the public surface static, no-JS, and auditable.

The system is intentionally split into two surfaces:

  • Public profile surface: static HTML + CSS, no JS.
  • Dashboard surface: authenticated UI for editing profile data (may use JS).

High-level model

A public profile request resolves to:

  • Profile lookup (by username or custom domain)
  • Pure HTML render (deterministic function)
  • Static asset delivery (base CSS + theme CSS + avatar)

The public renderer has no framework/runtime dependencies at request time and produces a complete HTML document.

Request lifecycle (public profiles)

Username URL

GET /<username>

  1. Resolve username -> profile
  2. Load profile + links
  3. Render HTML via the pure renderer
  4. Return HTML
  5. Browser fetches:
    • /css/base.<hash>.css
    • /css/theme-<id>.<hash>.css
    • avatar image (if present)

Custom domain URL

GET https://<custom-domain>/

  1. Resolve host -> profile (custom domain mapping)
  2. Same render flow as username URL

Notes:

  • Custom domains should be implemented as rewrite, not redirect, so the canonical URL remains the user's domain.
  • The HTML is the same; only the origin differs.

Core components

1) Profile store

Stores:

  • profile identity (username, displayName, bio, avatarUrl)
  • theme selection (themeId) and sparse theme options (themeOptions)
  • plan (FREE | LIFETIME)
  • analytics flag (analyticsEnabled)
  • ordered links

Theme options are sparse (absence means default). This avoids leaking false booleans into the model and keeps raw JSON clean.

2) Pure renderer (HTML)

A deterministic function:

  • input: { profile, links, options }
  • output: { html, meta }

Properties:

  • No I/O inside the render function
  • Deterministic: same input => same HTML string
  • Escaping: user content is HTML-escaped
  • No JS: renderer never emits <script> tags for public pages

3) CSS system (base + theme)

CSS is split into:

  • base CSS: shared component styles (sb-* primitives)
  • theme CSS: variables + deltas for a single theme

At build time:

  • CSS files are fingerprinted by content hash
  • a manifest maps theme id -> hashed path

At request time:

  • renderer emits <link rel="stylesheet"> tags for the base file and selected theme file
  • files are served with immutable caching headers

This allows many themes without shipping all CSS to every profile.

4) Redirect tracking (optional analytics)

Link click tracking is implemented as a server-side redirect layer:

  • Public profile links either point directly to link.url (analytics off)
  • Or point to a same-origin redirect endpoint (analytics on), e.g. /r/<linkId>

The redirect endpoint:

  • records a click event (server-side)
  • responds with an HTTP redirect to the final destination

No JS or third-party scripts are required.

HTML structure contract

Themes should not require per-theme markup. The renderer emits a stable skeleton:

  • .sb-card-shell wrapper
  • .sb-card main container
  • .sb-avatar (optionally wrapped, theme-specific only when needed for layout invariants)
  • .sb-name, .sb-handle, .sb-bio
  • .sb-links containing .sb-button links
  • .sb-footer (optional branding)

Themes should primarily use:

  • variables
  • scoped overrides
  • pseudo-elements

Avoid theme-specific DOM where possible; it increases renderer complexity and makes themes harder to evolve.

Caching strategy

Public HTML

You may choose one of two strategies (both compatible with this architecture):

  • Short-lived caching (safer during rapid iteration)
  • Conditional caching keyed by profile updatedAt / content hash

The renderer returns a content hash (meta.contentHash) which can be used as an ETag or cache key.

CSS and static assets

  • Fingerprinted filenames: base.<hash>.css, theme-<id>.<hash>.css
  • Serve with: Cache-Control: public, max-age=31536000, immutable

This makes repeat profile loads extremely cheap, and avoids bloating HTML responses with inline CSS.

Environments

production / preview

  • public profiles link CSS via <link> tags (cacheable)
  • no JS on public pages

export

  • can inline base + theme CSS into the HTML for a true single-file artifact
  • still no JS

Known tradeoffs

  • Two CSS requests is intentional. We prefer cacheability and long-lived immutability over "single-response pages."
  • Theme count scales file count. You'll have more static assets, but each is shared and cached across all profiles using that theme.
  • Deterministic rendering limits per-view personalization. This is a deliberate constraint for auditability and performance.
  • Some themes may need minimal wrapper markup. Keep exceptions rare and documented; the default should be "same skeleton, different CSS."

Contract tests (architecture-level)

These invariants should be regression-tested:

  • Public HTML contains 0 <script> tags.
  • Public HTML loads ≤ 2 CSS files (base + theme).
  • All resource URLs are same-origin (static.bio or custom domain).
  • Renderer output is deterministic for identical inputs.
  • CSS manifest generation produces stable hashed filenames for stable content.