# Architecture decisions Why the site is built the way it is. Read this before questioning one of these decisions — each records the reasoning, not just the choice. Operational how-to lives in `docs/deployment.md`; open work lives in the Gitea issues (milestone „Elternbeirat-Website"). > **Note on scope.** The site started as a static information site with content > as files (stage 1). Editorial maintenance without a developer then replaced > that file layer with PocketBase, which revised **AE-2** and **AE-3** below — > each now records both the original decision and why it changed. ## AE-1: Blazor with static server-side rendering, not WebAssembly **Decision:** Blazor Web App with interactivity mode *None* (pure static SSR). Target framework .NET 10 (LTS). **Rationale:** Blazor WASM loads several MB of runtime before the first visible letter, serves search engines and link previews (WhatsApp, Signal, Messenger — the main distribution channel among parents) an empty shell, and provides no value on a pure information site. Static SSR delivers finished HTML, needs no JavaScript, and costs around 60 MB of RAM in the container. **Consequence:** Interactivity can be added later per component (`@rendermode InteractiveServer` on exactly that one component), without changing the architecture. Never global. ## AE-2: Content in PocketBase, read per request (revised) **Decision:** All visible content lives in **PocketBase** (collections `pages`, `posts`, `events`, `faq_topics`, `faqs`); the app reads it over the REST API per request via a typed `PocketBaseClient`. Documents remain PDFs under `wwwroot`. **Original decision (stage 1):** Content as files in the repo — Markdown with YAML frontmatter, events as `Content/events.yml`. The rationale then was: no DB backup, no migration schema, no file/DB drift, and "backup = `git clone`". For a handful of pages that was the leanest option. **Why it changed:** The site needs to be maintainable by the board **without a developer and without a redeploy**. Files meant every text fix was a commit and a rebuild. PocketBase keeps the "small, self-hostable, one binary" spirit while letting an editor change content live in its admin UI. It has **no** migration schema of the EF/ORM kind — the collections are its data — so the objection that weighed against "a database" in stage 1 does not apply here. **Consequence:** Editing is now a task in the PocketBase admin, not a commit (see `redaktion.md`). The app holds no content of its own; if PocketBase is unreachable a page degrades to empty rather than failing. "Backup = `git clone`" no longer covers the content — the backup is now `pb_data` (see AE-3 and `deployment.md`). ## AE-3: Content lives in PocketBase's `pb_data`, not baked into the image (revised) **Decision:** The editable content lives in PocketBase's `pb_data`, a persistent volume separate from the app image. Only the static assets under `wwwroot/` (CSS, fonts, PDFs) are baked into the image. **Original decision (stage 1):** `Content/` and `wwwroot/` were both baked into the image, deliberately **not** a mounted volume, so the live state could never drift from the repo and "backup = `git clone`" held. **Why it changed:** Editing content live (AE-2) is only possible if that content survives a redeploy — so it must be a volume, not part of the image. The drift concern is answered differently now: the content simply has one home (PocketBase), and the repo no longer claims to hold it. The app image stays stateless and can be rebuilt and redeployed at any time without touching the data. **Consequence:** The backup is a copy of `pb_data` (plus a restore test), not a `git clone` (set up in issue #9, see `deployment.md`). The app reads content per request over HTTP, so there is nothing cached at startup and no `FileSystemWatcher` — an editorial change is live immediately, no restart needed. ## AE-4: TLS and certificates exclusively in the Nginx Proxy Manager **Decision:** The container speaks only HTTP on port 8080 and has **no** port mapping to the outside. NPM terminates TLS and is the only path to the container. **Rationale:** Matches the pattern already established in the home lab (`cloud.anticarnist.de`). Certificate management stays in one place. **Consequence (important):** In `Program.cs`, **no** `UseHttpsRedirection()` and **no** `UseHsts()` — otherwise a redirect loop behind the proxy. NPM sets HSTS. `UseForwardedHeaders` with emptied `KnownNetworks`/`KnownProxies` is correct because the container has no port mapping and is reachable only through NPM. ## Rejected alternatives (for AE-1) | Alternative | Why not | |---|---| | Blazor WASM | Payload, SEO, link previews, no benefit | | ASP.NET Core MVC/Razor Pages | Works too, but Razor Components are the more modern model | | Statiq.Web (C# SSG) → nginx | Leanest to operate, but every text change forces a build. Kept as a fallback. | | Astro/Hugo | More mature SSG ecosystem, but unfamiliar territory |