09 — Viewer Template

The public site every creator's CF Pages renders. Reads from the creator's public repo + theme.json at build time; outputs a static SPA + per-post pages.


What it ships

For each creator's public repo, the viewer-template build produces:

dist/
├── index.html              — home feed
├── posts/
│   └── <slug>/
│       └── index.html      — per-post pages
├── about/
│   └── index.html          — creator bio
├── feed.xml                — RSS mirror of feed.json
├── sitemap.xml             — for search engines
├── assets/                 — theme CSS + JS + images
└── opengraph/
    └── <slug>.png          — pre-generated OG images (optional)

Plus the raw feed.json and the post markdown bodies are served at their original paths (/feed.json, /posts/<slug>.md) so the indexer can pull them.


Build inputs (from the creator's public repo)

public-repo/
├── feed.json          ← canonical content list
├── posts/
│   ├── <slug>.md      ← body
│   └── <slug>.meta.json ← metadata
├── assets/            ← small media + thumbnails
├── theme.json         ← which theme + theme config
└── meta/
    └── about.md       ← (optional) creator bio

The viewer-template reads all of these at build time. The output is fully static — no runtime API calls back to GitHub or BKA infrastructure.


The build itself

Implementation question with two flavors:

Option A — 4G app with build-time data injection (recommended)

Same 4G stack as the BKA authoring app and Foundry. Vite + React + TypeScript. At build time, a custom Vite plugin reads the public repo's feed.json + posts/* + theme.json and bakes them into the bundle. The result is a pre-rendered static SPA: every post URL has its HTML pre-generated; React hydrates on the client for interactive bits (video player, search).

Pros: leverages 4G stack patterns the team already knows; same dependencies as everything else Cons: bigger bundle than necessary for what's mostly a content site

Option B — Pure static generator (Astro / Eleventy / custom)

A small Node script (or Astro project) that walks the public repo and emits static HTML files. No React. No SPA. No hydration.

Pros: smaller bundle, faster TTFB, simpler debugging Cons: another stack to maintain; doesn't reuse 4G patterns

Recommendation: start with Option A for stack consistency and migrate to B later if bundle size becomes a real concern. Most creator sites won't have the traffic to make Option B's perf matter.

Either way, the output is identical-shaped static HTML the visitor's browser loads.


Routes

Route Source Notes
/ feed.json Home feed, paginated 20 per page, newest first
/posts/<slug> posts/<slug>.md + .meta.json Per-post page
/about meta/about.md (or default) Creator's bio + social links
/feed.json static copy of input For the indexer (and any future clients)
/feed.xml generated from feed.json RSS 2.0 for traditional readers
/sitemap.xml generated For search engines
/tag/<tag> filtered feed.json Tag landing page
/archive full chronological list All posts, no pagination

Optional later:


Themes

Bundled themes (final list TBD; tentative starting set):

Theme Vibe Best for
Notebook Warm serif body, generous whitespace, paper-cream bg Essays, long-form writing
Magazine Editorial layout, big images, two-column blocks Mixed media, photo-heavy
Terminal Monospaced everywhere, green-on-black, scanlines Devs, hackers, niche
Studio Video-thumbnail grid, dark bg, autoplay-on-hover Video creators
Audio Podcast-shaped, big waveform players, episode list Podcasters
Minimal No theme, system fonts, no images styled Markdown-purist creators

Each theme is a directory under viewer/themes/<name>/ shipped with the viewer-template. The creator's theme.json picks one + optionally overrides colors/fonts:

{
  "theme": "notebook",
  "accent_color": "#0d6e6e",
  "display_font": "Inter",
  "body_font": "Charter"
}

Theme switching is build-time. Changing themes = update theme.json + push = CF rebuilds = new look live.


What the viewer does at runtime

Almost nothing. The HTML is pre-generated, served from CF edge cache, parsed by the browser. The tiny JS bundle (~10-30KB gzipped) hydrates only these interactive bits:

Zero runtime AI. Zero analytics that phone home (creator can add Plausible / Fathom / nothing). Zero tracking pixels by default. The site is content, not surveillance.


SEO + metadata

Each post page renders:

/sitemap.xml for crawlers. RSS at /feed.xml. Standard web hygiene; nothing exotic.


Accessibility baseline

Defaults all themes meet:

Themes that deviate (e.g., the Terminal theme's green-on-black) need to provide a high-contrast variant.


Customization beyond themes

Creators who want more than theme.json can fork the viewer-template into their public repo. They commit to their fork; the build uses their version instead of the default. Maintenance burden moves to them.

This is the right escape hatch — most creators won't need it, but the ceiling is "fork and own."


Versioning

The viewer-template is published as a versioned dependency (npm or git submodule reference) in the creator's public repo. By default:

Creators can pin to an older version if they want to avoid changes. Same opt-in update story as any npm dep.


Build performance budget

Target: build time under 60 seconds for a creator with up to 1000 published posts.

Optimizations that get us there:

For creators above ~5000 posts (rare), we may need to switch to Option B (static generator) or shard the build. Cross that bridge when (if) we reach it.


Comparison to alternatives the creator might have considered

Tool What it gives What BKA viewer adds over it
WordPress Hosted CMS + theming Self-hosted, no DB-server, no plugin attack surface
Substack Hosted newsletter + posts Self-owned domain + content; no platform clawback
Ghost Self-hosted CMS No Node server to run; CF Pages + GitHub = zero server
Hugo / Eleventy Static SSG No CLI to install; the authoring app + auto-pipeline handle everything
Custom Astro project Modern SSG with components Same idea, but you don't have to set it up

The viewer template is "what a non-developer creator would build if they could." We just ship it pre-built.