React-Frontend ausliefern, altes Vanilla-Frontend entfernt

- frontend.go: Seitenrouten liefern die index.html (no-cache), gehashte
  Assets unter /assets dauerhaft cachebar, sonst echtes 404
- /stats als Seite, /stats.html leitet per 301 weiter
- Containerfile: Node-Stage baut das Frontend nach web/
- Vite baut nach ../web (gitignored); web/ (Vanilla-JS) gelöscht
- notes/frontend.md, deploy.md angepasst

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-09-26 23:49:20 +02:00
co-authored by Claude Opus 5.5
parent 728ac9b45e
commit 9cac33d167
22 changed files with 129 additions and 1368 deletions
+23 -18
View File
@@ -19,9 +19,9 @@ import (
)
func main() {
// KVER_DIR: Verzeichnis mit web/ und static/. Per Chdir werden alle
// relativen Pfade (auch Uploads) auf einmal verankert -- die App ist damit
// nicht mehr an das Start-CWD gebunden.
// KVER_DIR: Verzeichnis mit web/ (gebautes Frontend) und static/. Per
// Chdir werden alle relativen Pfade (auch Uploads) auf einmal verankert --
// die App ist damit nicht mehr an das Start-CWD gebunden.
if dir := os.Getenv("KVER_DIR"); dir != "" {
if err := os.Chdir(dir); err != nil {
log.Fatalf("KVER_DIR: %v", err)
@@ -102,8 +102,8 @@ func envOr(key, fallback string) string {
// Tests identisch verwendet.
//
// Aufteilung: alles unter /api spricht ausschließlich JSON -- auch 404, 405,
// Rate-Limit und Panics. Der Rest liefert die HTML-Seiten und Dateien des
// Frontends.
// Rate-Limit und Panics. Der Rest liefert das Frontend (frontend.go) und
// statische Dateien.
func routes() http.Handler {
r := chi.NewRouter()
// RealIP übernimmt X-Forwarded-For/X-Real-IP vom Reverse-Proxy, damit
@@ -166,22 +166,27 @@ func routes() http.Handler {
})
})
// --- HTML-Seiten ---
// Mit Impression-Tracking (anonymisiert, siehe stats.go). Nur echte
// Seitenaufrufe, nicht API/Assets.
r.With(trackImpression).Get("/", serveIndex)
r.With(trackImpression).Get("/e/{pid}", serveEntryPage)
r.With(trackImpression).Get("/u/{username}", serveUserPage)
// --- Frontend (React) ---
// Die Seitenrouten entsprechen denen des Client-Routers (frontend/src/
// main.tsx) und liefern alle dieselbe index.html. Impression-Tracking
// (anonymisiert, siehe stats.go) nur auf echten Seitenaufrufen, nicht auf
// API/Assets; die Statistik-Seite zählt wie bisher nicht mit.
r.With(trackImpression).Get("/", serveApp)
r.With(trackImpression).Get("/e/{pid}", serveApp)
r.With(trackImpression).Get("/u/{username}", serveApp)
r.Get("/stats", serveApp)
// Alte Adresse der Statistik-Seite (Lesezeichen, Links von außen).
r.Get("/stats.html", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/stats", http.StatusMovedPermanently)
})
r.Handle("/assets/*", serveAssets())
// Hochgeladene Medien + alte statische Assets. nosniff (global via
// secHeaders) verhindert, dass der Browser eine durchgereichte (nicht
// re-kodierte) Datei als HTML/JS interpretiert -> schließt
// Polyglot/Stored-XSS über Bild-Uploads.
// Hochgeladene Medien + statische Dateien (Schriften, Rechtstexte).
// nosniff (global via secHeaders) verhindert, dass der Browser eine
// durchgereichte (nicht re-kodierte) Datei als HTML/JS interpretiert ->
// schließt Polyglot/Stored-XSS über Bild-Uploads.
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Frontend-Dateien (JS, CSS, weitere Seiten)
r.Handle("/*", http.FileServer(http.Dir("web")))
return r
}