Refactor the site container from a TLS-owning, port-binding proxy into an internal plaintext static server that sits behind a dedicated edge reverse proxy, and add an automated build/deploy pipeline. - Caddyfile: single :80 listener (auto_https off), Host-matcher routing for apex/blog/art/code with /_astro+/assets re-root and www->apex redirect - Dockerfile: EXPOSE 80 only (TLS now handled by the edge proxy) - compose.yml: pull registry image, join external 'edge' network, declare routing via caddy-docker-proxy labels; drop build/ports/cert volumes - compose.dev.yml: local container preview on :8080 - .forgejo/workflows/deploy.yml: push-to-main -> desktop runner builds, pushes to the Forgejo registry, SSH-deploys to the VPS - docs + .dockerignore updated for the new topology Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
244 lines
18 KiB
Markdown
244 lines
18 KiB
Markdown
# jb-website — Live Planning Document
|
|
|
|
> Living doc. Updated as decisions are made. The goal is to capture intent, constraints, and the reasoning behind tech choices so we don't re-litigate them and don't accidentally box ourselves in.
|
|
|
|
## 1. Project intent
|
|
|
|
A personal website that serves as the canonical home for Josh Bairstow's writing, generative/digital art, and a public index pointing out to side-hustle businesses. First draft of what will actually go live — not a throwaway mockup. Must leave room for the work in §3 without major rewrites.
|
|
|
|
The design system, voice, and four reference pages (Home, Blog index, Post, Art focus view) are captured in the Claude Design handoff bundle. The visual target is locked; the implementation is open.
|
|
|
|
## 2. Confirmed requirements
|
|
|
|
### 2.1 Pages (initial scope)
|
|
- **Home / landing** — fused composition (intro + hero zone + selected writing + subdomain index). Three layout variants exist in the design (`split`, `stack`, `index`); only one ships as the canonical URL at launch.
|
|
- **Blog index** — typographic post archive grouped by year, with featured lead post.
|
|
- **Blog post** — reading template with editorial prose rhythm, pull-quote, inline figure, prev/next nav.
|
|
- **Art focus view** — single-piece immersive view with caption and spec rail; the work itself is a live generative canvas.
|
|
- **404 / missing-asset page** — on-brand "this doesn't exist yet" or "coming soon" page that links back home. Used for any nav target that hasn't been built yet.
|
|
|
|
### 2.2 Navigation
|
|
- All top-nav links must resolve: existing page if built, otherwise the 404/missing page; never a dead link.
|
|
- Subdomain index rows on the home page link out to side-hustle subdomains (see §2.5), not to internal routes.
|
|
|
|
### 2.3 Blog mechanics
|
|
- **Content source:** static markdown files committed to the repo (likely `content/posts/*.md` with frontmatter). No CMS.
|
|
- **Rendering:** the framework translates markdown → the design-system styling (Bodoni headings, Hanken body, small-caps lead-in, ochre links, etc.). Authors write content; layout is automatic.
|
|
- **Browsing:** archive view (already designed) groups by year with hover affordances.
|
|
- **Search:** basic — title + body + tag substring match is enough for v1. Not Algolia-grade.
|
|
- **Out of scope for v1:** comments, RSS bells & whistles, multi-author, drafts UI. RSS feed itself is cheap and worth shipping.
|
|
|
|
### 2.4 Art pages
|
|
- Heavy client-side JS is expected (canvas / WebGL / WebGPU / shaders).
|
|
- Each art piece should be able to ship its own dependencies without bloating other pages.
|
|
- The framework must allow per-page islands of JS without forcing the rest of the site to load them.
|
|
|
|
### 2.5 Subdomain side-hustles
|
|
- Distinct businesses (Code experiments, Signage, Keycaps, etc.) live on their own subdomains.
|
|
- Served by a **reverse proxy + container per subdomain** at the infrastructure layer.
|
|
- **Out of scope for this repo.** This codebase only needs to link out to them; it does not host or build them.
|
|
|
|
### 2.6 Auth (future, plan-for-not-build)
|
|
- Goal: gate certain pages or sections to me + named collaborators.
|
|
- Likely shape: low-friction login (passkey, magic link, or GitHub OAuth — TBD), middleware-protected routes, an allowlist of authorized identities.
|
|
- Not a v1 feature. Tech choice should not preclude adding this later.
|
|
|
|
### 2.7 Design system
|
|
- Source files from the handoff bundle (`colors_and_type.css`, `site.css`, SVG marks, type tokens) become the foundation.
|
|
- Components built in an **atomic design** style: atoms (typography, marks, hairlines) → molecules (post row, index row, eyebrow + title pair) → organisms (topbar, sitefoot, masthead, feature card) → templates (page layouts) → pages.
|
|
- **Brand system docs imported** to `docs/design-system/` (2026-06-01). Includes `colors_and_type.css`, `README.md` (governing idea, hard guardrails, voice, marks), `SKILL.md`, `assets/` (SVG marks + grain), `preview/` (specimen cards), and `ui_kits/home/` (reference JSX components — Wordmark, HeroZone, LatestLine, SubdomainIndex). Treat as the canonical spec.
|
|
- **One rule to internalise** (from the brand README): *one base aesthetic and one accent, not equal partners.* Base ~90% — quiet luxury, warm browns + ink. Accent — rare, contained, ochre. Litmus test for any decision: *barely noticed on visit one, a small delight on visit three.*
|
|
|
|
## 3. Future work that must not be hamstrung
|
|
|
|
These are not v1, but the v1 choices must leave room for them:
|
|
|
|
1. Heavier JS art pieces — multiple pieces, each potentially with its own libraries.
|
|
2. Auth-gated areas.
|
|
3. RSS, sitemap, OG cards.
|
|
4. Maybe per-post comments or a guestbook (low priority).
|
|
5. Possible analytics (privacy-respecting).
|
|
6. View transitions / page-to-page animation polish.
|
|
|
|
## 4. Tech decision
|
|
|
|
### 4.1 Choice: **Astro 5 + React islands**
|
|
|
|
Astro is a static-first site builder that:
|
|
- Ships zero JS by default — matters for the home/blog/post pages which are content, not apps.
|
|
- Allows **islands** of any framework (React, Svelte, vanilla) on a per-component basis, hydrated independently. The art pages can be React (or vanilla canvas) without forcing React onto the blog.
|
|
- Has first-class markdown / MDX content collections with typed frontmatter — the blog requirement maps to it directly.
|
|
- Has file-based routing, `404.astro`, and middleware for future auth.
|
|
- Supports SSR per-route when needed (for auth-gated pages later), while keeping the rest SSG.
|
|
- Works with [Pagefind](https://pagefind.app) for build-time blog search at near-zero runtime cost.
|
|
|
|
### 4.2 Alternatives considered
|
|
|
|
| Option | Why not |
|
|
|---|---|
|
|
| Plain HTML/CSS/JS | Forces hand-rolling the markdown pipeline, search index, component reuse, future auth middleware. Looks "simple" but pays for that simplicity later. |
|
|
| Next.js | Heavier baseline; React-everywhere assumption taxes every page. Worth it if most pages were app-shaped, but here they're document-shaped. Adds friction for markdown content collections. |
|
|
| SvelteKit | Solid technically; user's note about "larger react framework" suggests React is the preferred ecosystem for the heavy bits. Astro lets us still use React where it matters. |
|
|
| Remix / React Router 7 | SSR-first, optimized for app-style data flows. Mismatch with the document-heavy nature of this site. |
|
|
|
|
### 4.3 Implications of the Astro choice
|
|
|
|
- **Hosting:** flexible — static build works on Cloudflare Pages, Netlify, Vercel, or self-hosted Nginx behind the reverse proxy. SSR (for auth later) wants a Node/edge runtime; Cloudflare Pages + Workers or self-hosted Node both work.
|
|
- **Auth path:** Astro middleware + Auth.js or Lucia when the time comes. Adds SSR mode for protected routes; rest of site stays SSG.
|
|
- **Search path:** Pagefind runs at build time; outputs a static index the client fetches lazily. No server needed.
|
|
- **Subdomain wiring:** unchanged — this repo only needs to render external links with the correct hrefs. Reverse proxy does the rest.
|
|
|
|
## 5. Proposed repo shape (draft, will evolve)
|
|
|
|
```
|
|
jb-website/
|
|
├── docs/
|
|
│ ├── planning.md # this file
|
|
│ └── design-system/ # imported brand system docs
|
|
├── public/
|
|
│ ├── assets/ # SVG marks, grain texture, favicons
|
|
│ └── og/ # generated OG images
|
|
├── src/
|
|
│ ├── components/
|
|
│ │ ├── atoms/ # Eyebrow, Mark, Hairline, Wordmark, Coord
|
|
│ │ ├── molecules/ # PostRow, IndexRow, MetaLine, FeatureCard
|
|
│ │ ├── organisms/ # TopBar, SiteFoot, Masthead, HeroZone
|
|
│ │ └── islands/ # React/JS-heavy: ArtCanvas, HeroPointer, Search
|
|
│ ├── layouts/
|
|
│ │ ├── BaseLayout.astro # html, head, grain, marks, topbar, footer
|
|
│ │ ├── PostLayout.astro # reading template; used by markdown posts
|
|
│ │ └── ArtLayout.astro # immersive focus view
|
|
│ ├── content/
|
|
│ │ ├── config.ts # collection schemas (zod)
|
|
│ │ ├── posts/ # markdown blog posts
|
|
│ │ └── art/ # markdown for art pieces; canvas in islands/
|
|
│ ├── pages/
|
|
│ │ ├── index.astro # home
|
|
│ │ ├── blog/
|
|
│ │ │ ├── index.astro # archive
|
|
│ │ │ └── [slug].astro # post detail (renders content/posts)
|
|
│ │ ├── art/
|
|
│ │ │ ├── index.astro # listing (future)
|
|
│ │ │ └── [slug].astro # focus view
|
|
│ │ └── 404.astro # missing / coming-soon page
|
|
│ ├── styles/
|
|
│ │ ├── colors_and_type.css # from handoff bundle
|
|
│ │ └── site.css # shared chrome
|
|
│ └── middleware.ts # placeholder for future auth
|
|
└── astro.config.mjs
|
|
```
|
|
|
|
## 6. Domain & deployment architecture
|
|
|
|
### 6.1 Domain layout (confirmed)
|
|
|
|
- **Apex:** `joshbairstow.com` — the home / landing page (this repo's primary surface).
|
|
- **Personal subdomains (this repo serves them — "in-repo paths"):**
|
|
- `blog.joshbairstow.com` — writing archive + posts
|
|
- `art.joshbairstow.com` — generative & digital art
|
|
- `code.joshbairstow.com` — code experiments & small tools (confirmed in-repo)
|
|
- The architecture must remain open to adding more in-repo subdomains later without restructuring (e.g. a `notes.`, `talks.`, or similar). The host-aware routing model in §6.2 generalises to any number of additional paths.
|
|
- **External-container subdomains (separate repos / containers, out of scope here):**
|
|
- At least one side-hustle is confirmed for a separate container; signage and/or keycaps may use brand-name apex domains rather than `*.joshbairstow.com`.
|
|
- Each external container plugs into the same reverse proxy.
|
|
|
|
### 6.2 Multi-subdomain serving — decision needed
|
|
|
|
Three subdomains (`apex`, `blog.`, `art.`) need to be served by *this* repo. Two viable shapes:
|
|
|
|
**Option A — One Astro project, host-aware routing (recommended).** One codebase, one container. Routes live under `/`, `/blog`, `/art`. The reverse proxy maps host headers:
|
|
|
|
```
|
|
joshbairstow.com/* → backend /*
|
|
blog.joshbairstow.com/* → backend /blog/*
|
|
art.joshbairstow.com/* → backend /art/*
|
|
```
|
|
|
|
Pros: shared design system at compile time, single build, single deploy, single search index, no duplication. Internal cross-subdomain links use absolute URLs.
|
|
Cons: a little proxy config; you must remember to use absolute URLs when crossing subdomains in author-facing markdown.
|
|
|
|
**Option B — One Astro project per subdomain.** Three containers, three builds, design system imported as a shared package. More isolation; more deployment overhead. Worth it only if the subdomains diverge structurally (which they shouldn't).
|
|
|
|
**Default plan:** Option A. The routing rule generalises — any new in-repo subdomain (e.g. `notes.joshbairstow.com`) becomes a new path in the Astro project plus one extra block in the Caddyfile. The `code.` subdomain confirmed in-repo joins `blog.` and `art.` under the same scheme.
|
|
|
|
### 6.3 Hosting target — confirmed VPS + containers
|
|
|
|
Recommendation for a project of this size:
|
|
|
|
**Confirmed:** Vultr (Sydney region) — $6/mo droplet (1 vCPU / 1 GB) sufficient for a static site + a few containerised side-hustles; bumps to $12 / 2 GB if memory pressure arises. Sydney PoP keeps latency tight for AU readers and still serves global traffic acceptably.
|
|
|
|
**Suggested stack on the VPS:**
|
|
- **Caddy** as the reverse proxy — automatic HTTPS via Let's Encrypt, dead-simple Caddyfile, handles the host-header → path-prefix routing cleanly.
|
|
- **Docker + docker compose** — the Astro static output baked into a tiny container (Caddy + the `dist/` files, or just nginx-alpine + static files); side-hustle containers added alongside under the same compose file.
|
|
- **Deployment** (decided 2026-06-02 — see §6.5):
|
|
- Self-hosted **Forgejo** on the VPS (`git.joshbairstow.com`) hosts the repo + CI + a container registry; a **Forgejo Actions runner on the home desktop** builds the image and SSH-deploys to the VPS. Earlier GHCR/GitHub-Actions options were dropped in favour of self-hosting (learning value + avoids GitHub).
|
|
|
|
### 6.4 Other open questions
|
|
|
|
- **Tweaks panel:** drop it (it's a design-time tool), or keep a slimmed runtime theme toggle (Ink / Bone)? Default: drop for v1; theme is dark-ink only.
|
|
- **Auth shape:** passkey vs. magic link vs. GitHub OAuth. Deferrable.
|
|
- **Analytics:** if any, which (Plausible self-hosted, Umami self-hosted, none).
|
|
- **`code.` subdomain content:** ~~is this also served from this repo~~ — confirmed in-repo (2026-06-01).
|
|
- **Design system docs source:** ~~URL 404'd~~ — imported successfully (2026-06-01); see `docs/design-system/`.
|
|
|
|
### 6.5 CI/CD + multi-project topology (decided 2026-06-02)
|
|
|
|
The site is one project among several that will eventually share the VPS via
|
|
subdomains (separate repos/containers each). To keep their lifecycles
|
|
independent, infrastructure splits in two:
|
|
|
|
- **`jb-infra` repo (bootstrapped manually on the VPS):** a dedicated
|
|
**caddy-docker-proxy** edge container owns 80/443, terminates TLS for all
|
|
hosts, and routes by **Docker labels** over a shared external `edge` network;
|
|
plus **Forgejo** (`git.joshbairstow.com`) for self-hosted git + Actions + a
|
|
container registry.
|
|
- **This repo:** refactored to an *internal* plaintext static server (drops
|
|
TLS/ACME/port-binding; keeps host→path + asset re-root in `Caddyfile`), joins
|
|
the `edge` network, and declares its hostnames via `caddy` labels in
|
|
`compose.yml`. CI lives in `.forgejo/workflows/deploy.yml`.
|
|
|
|
**Flow:** push to `main` → home-desktop runner builds the image → pushes to the
|
|
Forgejo registry → SSHes to the VPS → `compose pull && up -d`. The small VPS
|
|
never builds (build offloaded to the always-on desktop). New projects plug in by
|
|
joining `edge` + adding labels — no central config edits.
|
|
|
|
Rationale: self-hosting Forgejo is a deliberate learning investment and avoids
|
|
GitHub; the label-driven edge proxy scales to many independent repos cleanly;
|
|
offloading builds keeps the VPS on the cheap tier. RAM target bumped to 2 GB (or
|
|
1 GB + swap). See `jb-infra/README.md` for the full bootstrap runbook.
|
|
|
|
## 7. Decisions log
|
|
|
|
| Date | Decision | Rationale |
|
|
|---|---|---|
|
|
| 2026-06-01 | Adopt Astro 5 with React islands as the framework | Best fit for static-content + heavy-JS-on-some-pages mix; doesn't preclude future React, auth, or interactivity work |
|
|
| 2026-06-01 | Blog posts are markdown files in `src/content/posts/` rendered via Astro content collections | Matches stated requirement; gives typed frontmatter and design-system templating for free |
|
|
| 2026-06-01 | Side-hustle subdomains are out of scope for this repo; only outbound links are wired here | Confirmed — served by separate containers behind a reverse proxy |
|
|
| 2026-06-01 | Home page ships the `stack` (editorial, centered) layout as the canonical landing | User selection — leaning into the editorial feel for v1 |
|
|
| 2026-06-01 | Apex `joshbairstow.com` + `blog.` + `art.` (+ likely `code.`) served from this single Astro project; subdomain routing handled at the reverse proxy | Avoids duplicating the design system; one build, one deploy |
|
|
| 2026-06-01 | Hosting target: VPS + Docker + Caddy as reverse proxy, containerised deploy workflow | Per user direction; matches the side-hustle container pattern; Caddy gives us automatic HTTPS for free |
|
|
| 2026-06-01 | VPS provider: Vultr (Sydney region) | Best AU latency for the smallest tier; modern hardware; easy Docker setup |
|
|
| 2026-06-01 | `code.` subdomain is in-repo (third in-repo section alongside `blog.` and `art.`) | User confirmed; routing model already supports it |
|
|
| 2026-06-01 | Architecture must remain open to adding further in-repo subdomain paths later | User flagged this as a constraint; host-aware routing in §6.2 scales to N paths without rework |
|
|
| 2026-06-01 | Brand-system docs imported to `docs/design-system/` from a fresh handoff bundle | Establishes the canonical brand spec in-repo so future work can cite it |
|
|
| 2026-06-02 | Self-host git + CI via Forgejo on the VPS (`git.joshbairstow.com`); runner on the home desktop | Learning value + avoids GitHub availability issues; offloads builds from the small VPS |
|
|
| 2026-06-02 | Dedicated `caddy-docker-proxy` edge container owns TLS/routing via Docker labels; website becomes an internal server | Decouples each project's deploy; new subdomains/containers plug in with zero central edits |
|
|
| 2026-06-02 | Infra (edge proxy + Forgejo) split into a separate `jb-infra` repo, bootstrapped manually | Infra changes rarely; keeps the app repo's CI focused on the app |
|
|
|
|
## 8. Next steps
|
|
|
|
Approved & queued:
|
|
|
|
1. Scaffold the Astro project; port `colors_and_type.css` and `site.css` from the handoff into `src/styles/`; copy SVG assets into `public/assets/`.
|
|
2. Build `BaseLayout.astro` (topbar + footer + grain + marks). Topbar links resolve to absolute subdomain URLs so they work whether viewed at the apex or a subdomain.
|
|
3. Implement Home page in the `stack` layout (canonical landing). Hero zone keeps the pointer-tracked light gradient as the one signature motion moment.
|
|
4. Implement on-brand 404 / missing-asset page (any unbuilt internal route falls here).
|
|
5. Wire the markdown content collection + Post layout; port the reference post as the first real entry.
|
|
6. Implement Blog index against the collection.
|
|
7. Implement Art focus view; port the generative tide-study canvas as a React (or vanilla) island.
|
|
8. Wire Pagefind for blog search.
|
|
9. Containerise (Caddyfile + `dist/` in an alpine image). Write a sample `compose.yml` for the VPS with the host-header routing for `apex`, `blog.`, `art.`.
|
|
10. Defer: auth middleware, additional art pieces, RSS, OG images, `code.` subdomain content.
|
|
|
|
Blocked / waiting on user input:
|
|
- *(none — all blockers cleared as of 2026-06-01; ready to scaffold on user's go-ahead.)*
|