fix(seo): Sitemap mit Blog füllen, Titel und Descriptions reparieren, EN-Seite korrigieren #63

Merged
Phil merged 3 commits from fix/sitemap-titles-en-links into main 2026-09-06 20:53:16 +00:00
Owner

Schritt 2 von 3 gegen den Seobility-Report vom 06.09.2026. Behebt, was Google daran hindert, die Seite vollständig zu finden.

Closes #58

Was drin ist

1. Die Sitemap enthielt eine einzige URL

Ursache saß in der Datenbank, nicht im Code: die Sitemap-Konfiguration stammte vom 31.10.2024 und enthielt vier Bereiche — zwei davon zeigten auf gelöschte Sections und erzeugten leere Dateien, Blog und Blog-Index waren nie eingetragen worden. Der Blog kam nach dem Sitemap-Setup dazu.

Jetzt sind Blog, Blog-Index und die Blog-Kategorien drin, die verwaisten Einträge raus. Weil diese Einstellung in der Datenbank und nicht im Project-Config liegt, steckt sie als Migration (migrations/m260906_120000_fix_sitemap_config.php) im Repo — sie löst Sections über Handles statt über IDs auf und ist idempotent, damit ein frisches docker compose up denselben Zustand herstellt (Projektregel §5).

2. /en und /en/ waren zwei URLs für dieselbe Seite

/en lieferte 200 ohne Weiterleitung, das Canonical zeigte aber auf /en/. Der 301 sitzt jetzt gezielt in der nginx-Config für den EN-Site-Root. Bewusst nicht über Crafts addTrailingSlashesToUrls gelöst — das hätte auch /impressum zu /impressum/ gemacht und bestehende URLs gebrochen.

3. Kategorie-Seiten trugen den Titel der Fehlerseite

seo.twig gab jeder Seite ohne entry den 404-Titel. Das Kategorie-Archiv liefert 200 und hieß trotzdem „Philflow | Seite nicht gefunden". Templates können jetzt einen eigenen Titel setzen, die echte 404-Seite bleibt unberührt.

4. Leere Meta-Descriptions

Der Fallback zeigte auf ein Feld paragraph, das im Projekt nicht existiert — er war damit immer leer, und das Tag wurde trotzdem ausgegeben. Ein leeres Description-Tag ist schlechter als gar keins. Jetzt zieht der Fallback den Artikeltext bzw. Seiteninhalt, HTML-frei und gekürzt; ist nichts da, entfällt das Tag.

5. Die englische Startseite verlinkte auf die deutschen Rechtsseiten und zeigte eine andere Telefonnummer als die deutsche. Beides saß im Content, nicht im Template: ein Linkfeld war als fester URL-String statt als Element-Beziehung gepflegt, dazu ein Tippfehler im Telefon-Link. Korrigiert auf /en/impressum, /en/datenschutz und +49 155 63696346.

6. llms-full.txt existierte nur auf dem Server, nicht im Repo — Drift gegen Projektregel §5. Beide Dateien sind jetzt versioniert und enthalten alle 12 Blogartikel (6 je Sprache); jede darin genannte URL wurde auf 200 geprüft.

Belege (lokal gemessen und selbst nachgeprüft)

Sitemap-Index:  5 Dateien statt 2, keine verwaisten mehr
  sitemap_sections_8_0.xml    6 URLs   ← alle Blogartikel, vorher gar nicht vorhanden
  sitemap_sections_7_0.xml    1 URL    (Blog-Index)
  sitemap_sections_1_0.xml    1 URL    (Startseite)
  sitemap_sections_3_0.xml    0 URLs   (Unterseiten — korrekt, stehen auf noindex)
  sitemap_categories_3_0.xml  1 URL
  → 9 <loc>-Einträge, jeder mit EN-Alternate = 18 adressierte Seiten. Vorher: 1.

curl -sI /en           → 301 auf /en/     ✔ (vorher 200)
/impressum /blog /en/blog /en/  → alle weiterhin 200, keine Regression ✔
migrate/up zweimal     → idempotent, keine Duplikate ✔

<title> /blog/category/compound-engineering
  vorher: Philflow | Seite nicht gefunden
  jetzt:  Philflow | Compound Engineering        ✔

Descriptions auf 7 Seiten DE+EN: kein einziges leeres content="" mehr ✔
EN-Footer  → /en/impressum, /en/datenschutz     ✔ (vorher: deutsche Pfade)
DE-Footer  → /impressum, /datenschutz           ✔ unverändert
tel:       → +4915563696346 auf beiden Sprachen ✔
llms.txt / llms-full.txt → je 12 Artikel-URLs, alle 200 ✔

Beim Deploy zu beachten

Drei Dinge greifen nicht allein durch den Code-Sync:

  1. Migration ausführen: php craft migrate/up auf Production, sonst bleibt die Sitemap dort leer.
  2. nginx neu laden: der /en-Redirect liegt in der nginx-Config, der Container braucht einen Reload.
  3. Content nachziehen: die Korrekturen an Footer-Links und Telefonnummer stecken in der Datenbank. Entry-Content wird laut docs/DEPLOYMENT.md nicht über Project-Config-Sync übertragen — das muss separat nachgezogen werden.

Danach die Sitemap in der Search Console neu einreichen.

Was offen bleibt

  • Das Kategorie-Archiv hat weiterhin keine Description — Kategorien haben schlicht kein Textfeld dafür. Das braucht eine Entscheidung, keinen Code.
  • Der Kategorie-Titel ist auf beiden Sprachen deutsch („Compound Engineering"), weil der Kategoriename nicht übersetzt ist. Redaktionsarbeit.
  • Die Description von /impressum ist jetzt die Adresse aus dem Fließtext — nicht falsch, aber redaktionell besser zu fassen.
  • Die englischen Seiten stehen in der Sitemap nur als xhtml:link alternate, nicht als eigener <loc>. Google akzeptiert das; andere Suchmaschinen sind pingeliger. Bei Bedarf separat.

Offen aus Schritt 1: die Ladezeit der Startseite (kein Seiten-Cache vorhanden). Schritt 3 ist #59.

🤖 Generated with Claude Code

https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh

Schritt 2 von 3 gegen den Seobility-Report vom 06.09.2026. Behebt, was Google daran hindert, die Seite vollständig zu finden. `Closes #58` ## Was drin ist **1. Die Sitemap enthielt eine einzige URL** Ursache saß in der Datenbank, nicht im Code: die Sitemap-Konfiguration stammte vom 31.10.2024 und enthielt vier Bereiche — zwei davon zeigten auf **gelöschte** Sections und erzeugten leere Dateien, **Blog und Blog-Index waren nie eingetragen worden**. Der Blog kam nach dem Sitemap-Setup dazu. Jetzt sind Blog, Blog-Index und die Blog-Kategorien drin, die verwaisten Einträge raus. Weil diese Einstellung in der Datenbank und nicht im Project-Config liegt, steckt sie als **Migration** (`migrations/m260906_120000_fix_sitemap_config.php`) im Repo — sie löst Sections über Handles statt über IDs auf und ist idempotent, damit ein frisches `docker compose up` denselben Zustand herstellt (Projektregel §5). **2. `/en` und `/en/` waren zwei URLs für dieselbe Seite** `/en` lieferte 200 ohne Weiterleitung, das Canonical zeigte aber auf `/en/`. Der 301 sitzt jetzt gezielt in der nginx-Config für den EN-Site-Root. Bewusst **nicht** über Crafts `addTrailingSlashesToUrls` gelöst — das hätte auch `/impressum` zu `/impressum/` gemacht und bestehende URLs gebrochen. **3. Kategorie-Seiten trugen den Titel der Fehlerseite** `seo.twig` gab jeder Seite ohne `entry` den 404-Titel. Das Kategorie-Archiv liefert 200 und hieß trotzdem „Philflow | Seite nicht gefunden". Templates können jetzt einen eigenen Titel setzen, die echte 404-Seite bleibt unberührt. **4. Leere Meta-Descriptions** Der Fallback zeigte auf ein Feld `paragraph`, **das im Projekt nicht existiert** — er war damit immer leer, und das Tag wurde trotzdem ausgegeben. Ein leeres Description-Tag ist schlechter als gar keins. Jetzt zieht der Fallback den Artikeltext bzw. Seiteninhalt, HTML-frei und gekürzt; ist nichts da, entfällt das Tag. **5. Die englische Startseite verlinkte auf die deutschen Rechtsseiten** und zeigte eine andere Telefonnummer als die deutsche. Beides saß im **Content**, nicht im Template: ein Linkfeld war als fester URL-String statt als Element-Beziehung gepflegt, dazu ein Tippfehler im Telefon-Link. Korrigiert auf `/en/impressum`, `/en/datenschutz` und `+49 155 63696346`. **6. `llms-full.txt` existierte nur auf dem Server**, nicht im Repo — Drift gegen Projektregel §5. Beide Dateien sind jetzt versioniert und enthalten alle 12 Blogartikel (6 je Sprache); jede darin genannte URL wurde auf 200 geprüft. ## Belege (lokal gemessen und selbst nachgeprüft) ``` Sitemap-Index: 5 Dateien statt 2, keine verwaisten mehr sitemap_sections_8_0.xml 6 URLs ← alle Blogartikel, vorher gar nicht vorhanden sitemap_sections_7_0.xml 1 URL (Blog-Index) sitemap_sections_1_0.xml 1 URL (Startseite) sitemap_sections_3_0.xml 0 URLs (Unterseiten — korrekt, stehen auf noindex) sitemap_categories_3_0.xml 1 URL → 9 <loc>-Einträge, jeder mit EN-Alternate = 18 adressierte Seiten. Vorher: 1. curl -sI /en → 301 auf /en/ ✔ (vorher 200) /impressum /blog /en/blog /en/ → alle weiterhin 200, keine Regression ✔ migrate/up zweimal → idempotent, keine Duplikate ✔ <title> /blog/category/compound-engineering vorher: Philflow | Seite nicht gefunden jetzt: Philflow | Compound Engineering ✔ Descriptions auf 7 Seiten DE+EN: kein einziges leeres content="" mehr ✔ EN-Footer → /en/impressum, /en/datenschutz ✔ (vorher: deutsche Pfade) DE-Footer → /impressum, /datenschutz ✔ unverändert tel: → +4915563696346 auf beiden Sprachen ✔ llms.txt / llms-full.txt → je 12 Artikel-URLs, alle 200 ✔ ``` ## Beim Deploy zu beachten Drei Dinge greifen nicht allein durch den Code-Sync: 1. **Migration ausführen:** `php craft migrate/up` auf Production, sonst bleibt die Sitemap dort leer. 2. **nginx neu laden:** der `/en`-Redirect liegt in der nginx-Config, der Container braucht einen Reload. 3. **Content nachziehen:** die Korrekturen an Footer-Links und Telefonnummer stecken in der Datenbank. Entry-Content wird laut `docs/DEPLOYMENT.md` nicht über Project-Config-Sync übertragen — das muss separat nachgezogen werden. Danach die Sitemap in der Search Console neu einreichen. ## Was offen bleibt - **Das Kategorie-Archiv hat weiterhin keine Description** — Kategorien haben schlicht kein Textfeld dafür. Das braucht eine Entscheidung, keinen Code. - **Der Kategorie-Titel ist auf beiden Sprachen deutsch** („Compound Engineering"), weil der Kategoriename nicht übersetzt ist. Redaktionsarbeit. - Die Description von `/impressum` ist jetzt die Adresse aus dem Fließtext — nicht falsch, aber redaktionell besser zu fassen. - Die englischen Seiten stehen in der Sitemap nur als `xhtml:link alternate`, nicht als eigener `<loc>`. Google akzeptiert das; andere Suchmaschinen sind pingeliger. Bei Bedarf separat. Offen aus Schritt 1: die Ladezeit der Startseite (kein Seiten-Cache vorhanden). Schritt 3 ist #59. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Die seo_sitemap-Tabelle verwies auf zwei Sections, die es nicht mehr
gibt (leere sitemap-Dateien) und auf eine Category-Group-ID, die es
nie in dieser Form gab. Blog Index und Blog fehlten komplett, damit
lieferte die Sitemap effektiv nur eine URL statt aller Artikel.

Migration löst Sections/Category-Group über Handles statt IDs auf und
nutzt die ether/seo SitemapService-API (saveSitemap/getSitemap), damit
Diff/Delete-Verhalten und UID-Handling des Plugins erhalten bleiben.

Zusätzlich: /en lieferte 200 ohne Redirect, obwohl die Seite selbst
/en/ als Canonical ausweist. addTrailingSlashesToUrls ist global und
hätte auch /impressum (bewusst ohne Trailing Slash) gebrochen, daher
ein exact-match nginx-Redirect nur für die EN-Site-Root statt einer
globalen Craft-Einstellung.

Refs #58

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
llms-full.txt existierte nur live auf Production (867 Bytes Drift gegen
CLAUDE.md §5), jetzt 1:1 von philflow.io/llms-full.txt übernommen und
versioniert. Beide Dateien bekommen einen Blog-Abschnitt mit allen 6
DE- und 6 EN-Artikeln inkl. URL und Kurzbeschreibung, vorher war der
Blog dort nicht vertreten. Jede URL gegen die lokale Instanz (localhost:8090)
mit 200 verifiziert.

Refs #58

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Kategorie-Archive und andere Nicht-Entry-Routen trugen den 404-Titel, weil
seo.twig nur auf `entry` prüfte. Templates können jetzt optional `pageTitle`
vor dem Include setzen; ohne beides bleibt die echte 404-Seite unverändert.

Der globale Description-Fallback (SEO-Feldeinstellung, nicht das Plugin-
Setting — Feldsettings überstimmen die Plugin-Defaults) zeigte auf ein nicht
existierendes Feld (`category.paragraph`) und lieferte immer leeren Text.
Neuer Fallback nutzt articleTldr/articleLead für Blogartikel bzw. den ersten
Content-Block für Unterseiten (z.B. Impressum), sauber von HTML befreit und
auf 160 Zeichen gekürzt. Das Description-Meta-Tag wird jetzt komplett
weggelassen statt mit leerem content="" ausgegeben, wenn nichts vorhanden ist.

Refs #58

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Phil merged commit 878aca61cd into main 2026-09-06 20:53:16 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Phil/philflow.io!63
No description provided.