337 lines
14 KiB
Markdown
337 lines
14 KiB
Markdown
|
|
# Feature-Brainstorm: Verbreitung & Robustheit
|
|||
|
|
|
|||
|
|
_Stand: 2026-08-05 · Basis: v0.9.1 · Fokus: Open-Source-Verbreitung + Betrieb & Robustheit_
|
|||
|
|
|
|||
|
|
Dieses Dokument ergänzt [`ideas.md`](ideas.md) (Präsentations-Features) und
|
|||
|
|
[`brainstorm.md`](brainstorm.md) (Stand Mai 2026). Es wiederholt deren Ideen nicht,
|
|||
|
|
sondern nimmt eine andere Perspektive ein: **Was hindert andere Fotografen daran,
|
|||
|
|
Folio einzusetzen — und was geht im Betrieb kaputt?**
|
|||
|
|
|
|||
|
|
Alle Ideen sind gegen den tatsächlichen Code-Stand geprüft. Aufwand: XS (Stunden),
|
|||
|
|
S (1 Tag), M (2–5 Tage), L (Wochen).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Kontext: Was seit Mai dazugekommen ist
|
|||
|
|
|
|||
|
|
Aus `brainstorm.md` bereits erledigt: **Map-View** (`app/map/page.tsx`,
|
|||
|
|
`components/MapView.tsx`), Video-Support, Webhook-Invalidierung, Album-Beschreibungen.
|
|||
|
|
|
|||
|
|
Weiterhin offen aus den alten Listen (hier bewusst _nicht_ nochmal ausgeführt):
|
|||
|
|
RSS-Feed, Slideshow, Download-Button, Share-Link, Fullscreen, Timeline-View,
|
|||
|
|
Immich Smart Search, Photo Stories, PWA, Watermarking, Client-Proofing, EXIF-Analytics.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## A. Betrieb & Robustheit
|
|||
|
|
|
|||
|
|
### A1. Fehler-, Lade- und 404-Boundaries — **XS, hoher Impact**
|
|||
|
|
|
|||
|
|
**Befund:** Im gesamten `app/`-Verzeichnis existiert keine einzige `error.tsx`,
|
|||
|
|
`global-error.tsx`, `loading.tsx` oder `not-found.tsx`.
|
|||
|
|
|
|||
|
|
Weil alle Seiten `dynamic = 'force-dynamic'` sind und live gegen Immich laufen,
|
|||
|
|
heißt das: Immich neu gestartet, API-Key rotiert, Netzwerk kurz weg → der Besucher
|
|||
|
|
bekommt die generische Next.js-Fehlerseite. Ein Tippfehler in der URL → ebenfalls
|
|||
|
|
ungestylt. Für ein Produkt, dessen Verkaufsargument „schön" ist, ist das der
|
|||
|
|
peinlichste mögliche Zustand.
|
|||
|
|
|
|||
|
|
**Umsetzung:** `app/error.tsx` und `app/global-error.tsx` im Theme-Look mit
|
|||
|
|
Retry-Button; `app/not-found.tsx`; `loading.tsx` pro Route-Segment mit
|
|||
|
|
ThumbHash-Skeletons (`lib/thumbhash.ts` liefert die Bausteine schon).
|
|||
|
|
|
|||
|
|
**Warum zuerst:** Billigste Maßnahme mit der größten Wirkung auf den
|
|||
|
|
wahrgenommenen Reifegrad.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A2. Stale-while-error — Galerie bleibt online, wenn Immich weg ist — **M, hoher Impact**
|
|||
|
|
|
|||
|
|
**Befund:** `lib/cache.ts` ist ein 200-Einträge-LRU mit harter TTL (`CACHE_TTL`,
|
|||
|
|
Default 300s). Läuft ein Eintrag ab und Immich antwortet nicht, gibt es keinen
|
|||
|
|
Fallback — die Seite ist tot.
|
|||
|
|
|
|||
|
|
**Idee:** Abgelaufene Einträge nicht wegwerfen, sondern als *stale* markieren.
|
|||
|
|
Bei Upstream-Fehler den veralteten Eintrag trotzdem ausliefern, im Hintergrund
|
|||
|
|
erneut versuchen, und (optional konfigurierbar) einen dezenten Hinweis
|
|||
|
|
einblenden. Das öffentliche Portfolio überlebt damit ein Immich-Update,
|
|||
|
|
ein Backup-Fenster oder einen NAS-Reboot.
|
|||
|
|
|
|||
|
|
**Warum das zieht:** Genau das ist die Angst des Self-Hosters — „meine
|
|||
|
|
öffentliche Seite geht offline, weil mein privater Server hustet". Folio als
|
|||
|
|
_Puffer_ vor Immich zu positionieren statt als bloßen Proxy ist ein echtes
|
|||
|
|
Argument.
|
|||
|
|
|
|||
|
|
**Anker:** `lib/cache.ts`, `lib/immich.ts` (Request-Coalescing ist schon da und
|
|||
|
|
passt gut dazu).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A3. Persistenter Bild-Cache auf Platte — **M, hoher Impact auf schwacher Hardware**
|
|||
|
|
|
|||
|
|
**Befund:** `streamAsset()` proxied jedes Bild bei jedem Kaltstart erneut von
|
|||
|
|
Immich. Der LRU-Cache hält nur Metadaten, keine Bytes.
|
|||
|
|
|
|||
|
|
**Idee:** Optionales Cache-Volume (`CACHE_DIR`), in dem proxied Thumbnails und
|
|||
|
|
Previews pro Größen-Tier landen. Die Asset-Tokens sind bereits deterministisch
|
|||
|
|
(`lib/tokens.ts`: gleiche UUID → gleicher Token), also ist der Cache-Key
|
|||
|
|
geschenkt. Mit LRU-Eviction und konfigurierbarem Größenlimit.
|
|||
|
|
|
|||
|
|
**Warum:** Die typische Zielgruppe hostet auf Synology, RasPi oder einem alten
|
|||
|
|
NUC. Dort ist wiederholtes Proxying der Flaschenhals — und es hält gleichzeitig
|
|||
|
|
den Immich-Server unter Last.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A4. ISR statt `force-dynamic`, getrieben vom vorhandenen Webhook — **M**
|
|||
|
|
|
|||
|
|
**Befund:** Alle Seiten sind `force-dynamic`. Gleichzeitig existiert bereits
|
|||
|
|
`POST /api/webhook` mit HMAC-Signatur und gezielter Cache-Invalidierung.
|
|||
|
|
|
|||
|
|
**Idee:** Seiten auf Next.js-Cache-Tags umstellen (`albums`, `album-<id>`) und
|
|||
|
|
den Webhook `revalidateTag()` aufrufen lassen statt nur den internen LRU zu
|
|||
|
|
leeren. Ergebnis: statisch schnelle Auslieferung, trotzdem sofort aktuell,
|
|||
|
|
sobald sich in Immich etwas ändert.
|
|||
|
|
|
|||
|
|
**Achtung:** Passwortgeschützte Subpages/Alben müssen dynamisch bleiben —
|
|||
|
|
die Cookie-Prüfung in `lib/auth.ts` darf nicht wegcachen. Das ist die eigentliche
|
|||
|
|
Arbeit an dieser Idee, nicht das Umstellen selbst.
|
|||
|
|
|
|||
|
|
_Stand in `ideas.md` schon als Einzeiler; hier mit dem Webhook als Auslöser
|
|||
|
|
konkretisiert._
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A5. Config-Doctor — **S, hoher Impact auf Support-Aufwand**
|
|||
|
|
|
|||
|
|
**Befund:** `GET /api/admin/status` existiert bereits als Ansatzpunkt. Es gibt
|
|||
|
|
aber keine Stelle, die typische Fehlkonfigurationen aktiv meldet.
|
|||
|
|
|
|||
|
|
**Idee:** Ein Diagnose-Panel im Admin (plus `npm run doctor` fürs Terminal), das
|
|||
|
|
prüft:
|
|||
|
|
|
|||
|
|
- `AUTH_SECRET` gesetzt und ausreichend lang?
|
|||
|
|
- `TRUSTED_PROXY_HOPS` plausibel? — tatsächlich eingehende `X-Forwarded-For`-Kette
|
|||
|
|
messen und mit dem konfigurierten Wert vergleichen. Ein falscher Wert legt das
|
|||
|
|
Rate-Limiting still oder macht es spoofbar; heute merkt das niemand.
|
|||
|
|
- Existieren alle Album-IDs aus `gallery.yaml` noch in Immich?
|
|||
|
|
- Reichen die Permissions des API-Keys?
|
|||
|
|
- Liegen Passwörter im Klartext statt als `scrypt:`-Hash? (`lib/auth.ts` warnt
|
|||
|
|
bislang nur in die Logs, wo es keiner liest.)
|
|||
|
|
- Ist `content/` beschreibbar? (war real ein Bug — siehe Commit-History)
|
|||
|
|
|
|||
|
|
**Warum:** Fast jedes „geht bei mir nicht"-Issue eines Self-Hosting-Projekts ist
|
|||
|
|
eine dieser Zeilen. Der Doctor beantwortet sie, bevor das Issue aufgemacht wird.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A6. `AUTH_SECRET` automatisch bereitstellen — **XS**
|
|||
|
|
|
|||
|
|
**Befund:** `lib/secret.ts` wirft in Production hart, wenn `AUTH_SECRET` fehlt.
|
|||
|
|
Korrekt gedacht (nicht auf eine ratbare Konstante zurückfallen), aber es
|
|||
|
|
bedeutet: `docker run` ohne gesetztes Secret → App startet nicht, mit einem
|
|||
|
|
Fehler, den Einsteiger nicht sofort einordnen.
|
|||
|
|
|
|||
|
|
**Idee:** Fehlt die Env-Variable, beim ersten Start ein Zufalls-Secret erzeugen
|
|||
|
|
und nach `content/.secret` (Mode 0600) persistieren. Weiterhin warnen, aber
|
|||
|
|
nicht blockieren. Env-Variable behält Vorrang.
|
|||
|
|
|
|||
|
|
**Trade-off, der dokumentiert gehört:** Das Secret liegt dann im Content-Volume
|
|||
|
|
statt in der Umgebung. Für ein Self-Hosted-Portfolio ist das der richtige
|
|||
|
|
Kompromiss — das Volume enthält ohnehin die Galerie-Passwörter.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A7. Strukturierte Logs + optionale Metriken — **S bis M**
|
|||
|
|
|
|||
|
|
Heute: `console.warn`-Prosa. Vorschlag: JSON-Logzeilen hinter `LOG_LEVEL`, plus
|
|||
|
|
optionales `GET /api/metrics` im Prometheus-Textformat mit Cache-Hit-Rate,
|
|||
|
|
Immich-Latenz-Histogramm, Rate-Limit-Treffern und Bytes durch den Bild-Proxy.
|
|||
|
|
|
|||
|
|
**Warum es sich lohnt:** Die Self-Hosting-Community betreibt Grafana. Ein
|
|||
|
|
Projekt, das ein fertiges Dashboard-JSON mitliefert, wirkt sofort erwachsen —
|
|||
|
|
und es ist wenig Arbeit, weil die Zählpunkte (`lib/cache.ts`, `lib/immich.ts`,
|
|||
|
|
`lib/rate-limit.ts`) schon zentralisiert sind.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### A8. Redis-Backend, aber nur optional — **M, niedrige Priorität**
|
|||
|
|
|
|||
|
|
Rate-Limiter und Cache sind ausdrücklich In-Memory und skalieren nicht über
|
|||
|
|
mehrere Instanzen. Ein optionales Backend hinter `REDIS_URL` würde das lösen.
|
|||
|
|
|
|||
|
|
**Ehrliche Einschätzung:** Die Zielgruppe fährt einen Container. Das ist
|
|||
|
|
Dokumentation eines bekannten Limits, kein dringendes Feature. Nur angehen, wenn
|
|||
|
|
konkret danach gefragt wird.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## B. Open-Source-Verbreitung
|
|||
|
|
|
|||
|
|
### B1. Setup-Assistent im Browser statt Anleitung — **M, größter Adoption-Hebel**
|
|||
|
|
|
|||
|
|
**Befund:** `components/SetupScreen.tsx` erklärt dem Nutzer, welche Dateien er
|
|||
|
|
per Hand kopieren und ausfüllen soll. Gleichzeitig kann das Admin-Panel bereits
|
|||
|
|
YAML atomar schreiben (`lib/admin/yaml-service.ts`) und alle Immich-Alben
|
|||
|
|
durchsuchen (`/api/admin/albums`) — die Bausteine für einen echten Assistenten
|
|||
|
|
liegen also schon da.
|
|||
|
|
|
|||
|
|
**Idee:** Aus dem Setup-Screen einen geführten Ablauf machen:
|
|||
|
|
|
|||
|
|
1. Immich-URL + API-Key eingeben → Verbindung sofort testen, Permissions prüfen
|
|||
|
|
2. Aus den gefundenen Shared Albums auswählen (visuelle Auswahl, keine UUIDs)
|
|||
|
|
3. Theme-Preset aus Vorschaukarten wählen (die Screenshots existieren in `docs/screenshots/`)
|
|||
|
|
4. Titel/Untertitel eintragen → fertig, Galerie steht
|
|||
|
|
|
|||
|
|
Schreibt `gallery.yaml`, `settings.yaml` und die Credentials. Absicherung: nur
|
|||
|
|
erreichbar, solange `needsSetup` gilt — danach dicht.
|
|||
|
|
|
|||
|
|
**Warum das die wichtigste Einzelmaßnahme ist:** Der Unterschied zwischen
|
|||
|
|
„funktioniert nach fünf Minuten" und „ich muss erst UUIDs aus der Immich-API
|
|||
|
|
kopieren" entscheidet, ob jemand dabei bleibt. Immich selbst hat diesen
|
|||
|
|
Onboarding-Standard gesetzt; Folio wird daran gemessen.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B2. Demo-Modus ohne Immich-Instanz — **S bis M**
|
|||
|
|
|
|||
|
|
**Befund:** Ohne laufenden Immich lässt sich Folio überhaupt nicht ansehen.
|
|||
|
|
Auch die E2E-Tests (`e2e/`) hängen deshalb an einer echten Instanz.
|
|||
|
|
|
|||
|
|
**Idee:** `DEMO_MODE=true` schaltet `lib/immich.ts` auf einen Fixture-Adapter mit
|
|||
|
|
mitgelieferten, frei lizenzierten Bildern um. Drei Effekte auf einen Schlag:
|
|||
|
|
|
|||
|
|
- Eine öffentliche Live-Demo, die man im README verlinken kann
|
|||
|
|
- E2E-Tests laufen deterministisch in CI, ohne Immich
|
|||
|
|
- Interessenten können `docker run` machen und sofort etwas sehen
|
|||
|
|
|
|||
|
|
**Warum:** Bei Self-Hosted-Software ist die Live-Demo der Konversionspunkt.
|
|||
|
|
Screenshots im README reichen nicht — Leute wollen durchklicken.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B3. Deploy-Templates für die Kanäle, wo die Zielgruppe sucht — **S**
|
|||
|
|
|
|||
|
|
Docker-Compose ist vorhanden. Was fehlt, sind die Verzeichnisse, in denen
|
|||
|
|
Immich-Nutzer tatsächlich stöbern: unRAID Community Applications, CasaOS- und
|
|||
|
|
Umbrel-App-Store, Portainer-Stack, Coolify-/Dokploy-Template.
|
|||
|
|
|
|||
|
|
Wenig Code, überwiegend Metadaten und PRs an fremde Repos. Aber es ist der
|
|||
|
|
Unterschied zwischen „muss man kennen" und „findet man".
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B4. Internationalisierung (DE/EN) — **M**
|
|||
|
|
|
|||
|
|
Steht in der alten Liste, hier aber mit anderer Begründung: nicht als
|
|||
|
|
Besucher-Komfort, sondern als Verbreitungs-Voraussetzung. Das Projekt hat
|
|||
|
|
deutsche Wurzeln (`app/impressum/page.tsx`, das `legal:`-Feld ist auf § 18 MStV
|
|||
|
|
gemünzt), die Oberfläche ist Englisch, und deutsche Nutzer stolpern über den
|
|||
|
|
Mix. Umgekehrt schreckt ein „Impressum" im Footer internationale Nutzer ab,
|
|||
|
|
solange nicht klar ist, dass es abschaltbar ist.
|
|||
|
|
|
|||
|
|
**Reihenfolge:** Erst die Besucher-Oberfläche, dann das Admin-Panel. Ohne
|
|||
|
|
Framework — ein einfacher Wörterbuch-Lookup gegen `settings.yaml: locale`
|
|||
|
|
genügt und passt zur Vanilla-CSS-/Kein-Framework-Linie des Projekts.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B5. Update-Hinweis im Admin-Dashboard — **XS bis S**
|
|||
|
|
|
|||
|
|
Version aus `package.json` gegen die GitHub-Releases-API prüfen (gecacht,
|
|||
|
|
täglich, abschaltbar) und im Admin-Dashboard dezent anzeigen. Standard bei
|
|||
|
|
Self-Hosted-Tools; Nutzer laufen sonst monatelang auf alten Versionen —
|
|||
|
|
insbesondere problematisch, weil es bereits Security-Releases gab (`37f7f62`).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B6. Themes teilbar machen — **S**
|
|||
|
|
|
|||
|
|
Export/Import von `settings.yaml` als „Theme-Paket" plus ein
|
|||
|
|
`themes/community/`-Verzeichnis im Repo, in das Nutzer per PR ihre Presets
|
|||
|
|
beisteuern.
|
|||
|
|
|
|||
|
|
**Warum:** Es schafft einen Beitragsweg für Leute, die kein TypeScript
|
|||
|
|
schreiben. Fotografen mit Gestaltungsanspruch sind genau die Zielgruppe — und
|
|||
|
|
jeder eingereichte Look ist zugleich Werbematerial.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B7. Tests für die API-Routen — **M**
|
|||
|
|
|
|||
|
|
`lib/__tests__/` deckt die Bibliotheken gut ab (Tokens, Auth, Rate-Limit,
|
|||
|
|
Config), `e2e/` deckt fünf Seiten ab. Die Routen selbst — Bild-Proxy,
|
|||
|
|
Auth-Endpoint, die sieben Admin-Routen — haben keine direkten Tests.
|
|||
|
|
|
|||
|
|
Das ist ausgerechnet die Schicht, an der die Sicherheitsgarantien des Projekts
|
|||
|
|
hängen: Allowlist-Durchsetzung, Token-Decoding, Rate-Limit-Namespaces,
|
|||
|
|
Admin-Session-Prüfung. Nach `37f7f62` („fix pre-auth bypasses") sollte hier eine
|
|||
|
|
Regressions-Suite liegen. Sie ist außerdem das, was Contributors Sicherheit
|
|||
|
|
gibt, überhaupt PRs zu stellen.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### B8. Kleinigkeit: Projektname in `package.json` — **XS**
|
|||
|
|
|
|||
|
|
`"name": "immich-lightbox"` bei einem Projekt namens Immich Folio. Fällt jedem
|
|||
|
|
Contributor beim ersten `npm install` auf.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## C. Ein größerer Differenziator
|
|||
|
|
|
|||
|
|
### C1. Mehrere Galerien aus einer Instanz — **L**
|
|||
|
|
|
|||
|
|
Heute: ein Container, ein Portfolio. Denkbar: mehrere Sites aus einer Instanz,
|
|||
|
|
je nach Host-Header, mit eigener `gallery.yaml`, eigenem Theme und eigener
|
|||
|
|
Domain — bei geteiltem Immich-Backend und geteiltem Bild-Cache.
|
|||
|
|
|
|||
|
|
**Zielgruppen:** Fotografen-Kollektive; Leute, die für Kunden hosten; jemand,
|
|||
|
|
der öffentliches Portfolio und private Familiengalerie trennen will.
|
|||
|
|
|
|||
|
|
**Ehrliche Einschätzung:** Deutlich mehr Aufwand als alles andere hier, weil
|
|||
|
|
Config-Singleton (`lib/config/index.ts`), Cache-Keys und Auth-Cookie-Namen alle
|
|||
|
|
mandantenfähig werden müssten. Nur sinnvoll, wenn tatsächlich Nachfrage
|
|||
|
|
entsteht — aber es wäre etwas, das kein vergleichbares Tool bietet. Als
|
|||
|
|
Beobachtungsposten notieren, nicht als Plan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Empfohlene Reihenfolge
|
|||
|
|
|
|||
|
|
**Erst (kleiner Aufwand, sofort sichtbar):**
|
|||
|
|
|
|||
|
|
1. A1 — Fehler-/Lade-/404-Boundaries · _XS_
|
|||
|
|
2. A6 — `AUTH_SECRET` automatisch bereitstellen · _XS_
|
|||
|
|
3. B8 — Projektname korrigieren · _XS_
|
|||
|
|
4. B5 — Update-Hinweis im Admin · _XS–S_
|
|||
|
|
|
|||
|
|
**Dann (die zwei Hebel, die den Unterschied machen):**
|
|||
|
|
|
|||
|
|
5. B1 — Setup-Assistent im Browser · _M_
|
|||
|
|
6. A2 — Stale-while-error · _M_
|
|||
|
|
|
|||
|
|
**Danach:**
|
|||
|
|
|
|||
|
|
7. A5 — Config-Doctor · _S_
|
|||
|
|
8. B2 — Demo-Modus · _S–M_
|
|||
|
|
9. B3 — Deploy-Templates · _S_
|
|||
|
|
10. A3 — Persistenter Bild-Cache · _M_
|
|||
|
|
11. B7 — API-Routen-Tests · _M_
|
|||
|
|
|
|||
|
|
**Später / nach Bedarf:** A4 (ISR), A7 (Logs & Metriken), B4 (i18n), B6 (Theme-Sharing),
|
|||
|
|
A8 (Redis), C1 (Multi-Site).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Roter Faden
|
|||
|
|
|
|||
|
|
Zwei Sätze, die die Auswahl zusammenhalten:
|
|||
|
|
|
|||
|
|
**Verbreitung** entscheidet sich in den ersten fünf Minuten — deshalb
|
|||
|
|
Setup-Assistent (B1) und Demo-Modus (B2) vor allem anderen.
|
|||
|
|
|
|||
|
|
**Robustheit** heißt bei Folio nicht Uptime der eigenen App, sondern
|
|||
|
|
Unabhängigkeit von der Verfügbarkeit des privaten Immich — deshalb ist
|
|||
|
|
Stale-while-error (A2) das strategisch wichtigste Backend-Feature, nicht ISR.
|