Social Cards / Open-Graph-Bilder pro Antrag (post-v2) #141

Closed
opened 2026-04-20 02:21:59 +02:00 by tobias · 1 comment
Owner

Kontext

Aus Redesign-Brief §11.2. Serverseitig gerenderte 1200×630-px-Bilder mit Score-Hero, Antrags-Titel und ECOnGOOD-Wortmarke, dynamisch pro Antrag unter /api/og/<antrag-id>.png.

Abhängigkeit: v2-Frontend (#139) stabil, ScoreHero-Komponente final.

Renderer-Optionen

Das Projekt ist Python-only ohne Node-Runtime. @vercel/og scheidet aus. Zwei Alternativen:

  1. Pillow (PIL) — pure Python, schnell, kein Headless-Browser. Schriftart-Rendering per ImageFont.truetype. Nachteil: kein automatisches Line-Breaking für lange Titel (manuell).
  2. Playwright + Headless-Chromium — bereits installiert (MEMORY: reference_playwright_chromium). Rendert /v2/og-template?antrag=X als Bild. Vorteil: identisches CSS mit v2. Nachteil: Chromium-Startup teuer (~300 ms pro Request).

Empfehlung: Playwright, mit aggressivem Caching. Die wenigen ms Startup sind durch Cache-Hits amortisiert. Option Pillow als Folge-Issue, falls Playwright-Cold-Start zu teuer.

Scope

  • Endpunkt GET /api/og/{drucksache}.png, 1200×630 px, PNG
  • Renderer: Playwright mit Template app/templates/v2/og-template.html
  • Template nutzt dieselben v2-Tokens und ScoreHero-Macro
  • Inhalte: Score groß, Verdict-Zeile („Vorbildlich — ..."), Antragstitel, Fraktionen, Bundesland · Drucksachennummer
  • Keine Parteienzitate im Bild (Urheberrecht, Kontext)
  • Avenir-Font-Lizenz für Server-Render klären ODER Nunito Sans als lizenzfreier Ersatz (Default)
  • Blob-Cache: cache/og/{drucksache}-{assessment_version}.png
  • Invalidierung bei Re-Analyse via og:updated_time
  • <meta property="og:image"> + <meta name="twitter:card" content="summary_large_image"> im Antragsdetail-Head

Cache-Strategie

  • File-basierter Cache in data/og-cache/ (Volume, nicht ins Repo)
  • Key: SHA-256 von (drucksache + updated_at)
  • TTL: unbegrenzt — Invalidierung nur bei Re-Analyse
  • Größe begrenzen via LRU-Cleanup-Script (nightly)

Tests

  • Request auf existierende Drucksache → 200 + PNG ≥ 10 KB
  • Request auf nicht existierende Drucksache → 404
  • Re-Analyse triggert Cache-Invalidation (neue ETag)
  • Facebook-Debugger + Twitter-Card-Validator grün (manuell)

Akzeptanzkriterien

  • Link auf Mastodon, Bluesky, LinkedIn, WhatsApp zeigt korrekte Vorschaukarte
  • Re-Analyse aktualisiert Bild binnen 5 Minuten
  • Facebook-Debugger und Twitter-Card-Validator grün
  • Avenir- oder Nunito-Sans-Rendering korrekt (kein Text-Fallback auf sans-serif)

Labels

frontend, seo, post-v2

## Kontext Aus Redesign-Brief §11.2. Serverseitig gerenderte 1200×630-px-Bilder mit Score-Hero, Antrags-Titel und ECOnGOOD-Wortmarke, dynamisch pro Antrag unter `/api/og/<antrag-id>.png`. **Abhängigkeit:** v2-Frontend (#139) stabil, ScoreHero-Komponente final. ## Renderer-Optionen Das Projekt ist Python-only ohne Node-Runtime. `@vercel/og` scheidet aus. Zwei Alternativen: 1. **Pillow (PIL)** — pure Python, schnell, kein Headless-Browser. Schriftart-Rendering per `ImageFont.truetype`. Nachteil: kein automatisches Line-Breaking für lange Titel (manuell). 2. **Playwright + Headless-Chromium** — bereits installiert (MEMORY: `reference_playwright_chromium`). Rendert `/v2/og-template?antrag=X` als Bild. Vorteil: identisches CSS mit v2. Nachteil: Chromium-Startup teuer (~300 ms pro Request). **Empfehlung:** Playwright, mit aggressivem Caching. Die wenigen ms Startup sind durch Cache-Hits amortisiert. Option Pillow als Folge-Issue, falls Playwright-Cold-Start zu teuer. ## Scope - Endpunkt `GET /api/og/{drucksache}.png`, 1200×630 px, PNG - Renderer: Playwright mit Template `app/templates/v2/og-template.html` - Template nutzt dieselben v2-Tokens und ScoreHero-Macro - Inhalte: Score groß, Verdict-Zeile („Vorbildlich — ..."), Antragstitel, Fraktionen, Bundesland · Drucksachennummer - **Keine Parteienzitate** im Bild (Urheberrecht, Kontext) - Avenir-Font-Lizenz für Server-Render klären ODER Nunito Sans als lizenzfreier Ersatz (Default) - Blob-Cache: `cache/og/{drucksache}-{assessment_version}.png` - Invalidierung bei Re-Analyse via `og:updated_time` - `<meta property="og:image">` + `<meta name="twitter:card" content="summary_large_image">` im Antragsdetail-Head ## Cache-Strategie - File-basierter Cache in `data/og-cache/` (Volume, nicht ins Repo) - Key: SHA-256 von (drucksache + updated_at) - TTL: unbegrenzt — Invalidierung nur bei Re-Analyse - Größe begrenzen via LRU-Cleanup-Script (nightly) ## Tests - Request auf existierende Drucksache → 200 + PNG ≥ 10 KB - Request auf nicht existierende Drucksache → 404 - Re-Analyse triggert Cache-Invalidation (neue ETag) - Facebook-Debugger + Twitter-Card-Validator grün (manuell) ## Akzeptanzkriterien - [ ] Link auf Mastodon, Bluesky, LinkedIn, WhatsApp zeigt korrekte Vorschaukarte - [ ] Re-Analyse aktualisiert Bild binnen 5 Minuten - [ ] Facebook-Debugger und Twitter-Card-Validator grün - [ ] Avenir- oder Nunito-Sans-Rendering korrekt (kein Text-Fallback auf sans-serif) ## Labels `frontend`, `seo`, `post-v2`
tobias added this to the post-1.0 milestone 2026-04-25 20:59:57 +02:00
Author
Owner

Bereits implementiert in app/og_card.py + Endpoint GET /api/og/.png in app/main.py. Pillow-basiert, mit Cache. Schließen als done.

Bereits implementiert in app/og_card.py + Endpoint GET /api/og/<drucksache>.png in app/main.py. Pillow-basiert, mit Cache. Schließen als done.
Sign in to join this conversation.
No description provided.