93 lines
4.9 KiB
Markdown
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 |
|