Files
Elternbeirat/docs/architektur.md
T

93 lines
4.9 KiB
Markdown

# 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 |