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:
/api/comments/<slug>— if creator opted into the comments worker/api/subscribe— if creator opted into the email subscribers worker
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:
- Video / audio players — native
<video>/<audio>tags with light progress-tracking JS; ranges handled by the browser - Search — client-side fuzzy search over the feed.json (pre-loaded for instant results)
- Tag filters — client-side filter on the home feed
- Comments (if creator opted in) — fetches from the creator's own comments worker
- Subscribe (if opted in) — POSTs to the creator's own subscribers worker
- Theme-day-night toggle — if the chosen theme supports both
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:
<title>from post title<meta name="description">from post summary- OpenGraph tags (title, description, image, type)
- Twitter Card tags
- Article structured data (JSON-LD)
<link rel="canonical">to the post's URL
/sitemap.xml for crawlers. RSS at /feed.xml. Standard web hygiene; nothing exotic.
Accessibility baseline
Defaults all themes meet:
- Semantic HTML (article, nav, main, footer)
- Skip-to-content link
- Keyboard nav for all interactive elements
- Sufficient color contrast (WCAG AA)
prefers-reduced-motionrespected- Alt text required on uploaded images (the BKA composer asks)
- Captions/transcripts UI for video / audio (creator-supplied; we don't auto-generate)
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:
- New creators get the latest stable version pinned
- The BKA authoring app can offer "your viewer template is outdated, want to update?" with a button that bumps the pin + commits
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:
- Incremental builds (only re-render changed pages on partial-content updates)
- Bundled theme CSS/JS shipped pre-built (creators don't re-bundle React from scratch)
- Image processing (thumbnails, WebP/AVIF conversion) is per-asset and parallelizable
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.