fix(seo): hreflang-Selbstbezug, ein robots-Tag, EN-Bilder und Retina-srcset #62

Merged
Phil merged 3 commits from fix/seo-hreflang-robots-assets into main 2026-09-06 20:22:22 +00:00
Owner

Schritt 1 von 3 gegen den Seobility-Report vom 06.09.2026 (Technik & Meta bei 8 %, -11 Punkte). Behebt die Befunde, die Sichtbarkeit kosten.

Refs #57

Was drin ist

1. hreflang zeigte auf Startseiten statt auf die Seite selbst (templates/_system/seo.twig)

entry.localized liefert in Craft per Definition nur die anderen Sites, nie die eigene — der Selbstbezug fehlte deshalb systematisch, und der Fallback in der alten Zeile 72 schickte ihn auf site.baseUrl. Auf entry-losen Seiten (Kategorie-Archiv, Paginierung) zeigten alle Verweise auf Startseiten.

Jetzt: die aktuelle Site nennt sich immer selbst mit ihrer echten URL. Existiert keine Übersetzung, wird für diese Sprache kein Tag geschrieben statt auf die Startseite zu zeigen. x-default zeigt auf die deutsche Fassung derselben Seite. Funktioniert auch für Kategorie-Seiten.

2. Zwei gegenläufige robots-Tags auf den Rechtsseiten (templates/_system/seo.twig)

index, follow war hart verdrahtet und wurde zusätzlich zum Wert aus dem SEO-Feld ausgegeben — /impressum und /datenschutz lieferten beides gleichzeitig. Jetzt genau ein Tag; der SEO-Feld-Wert gewinnt, index, follow nur als Fallback. unavailable_after wandert als Direktive in denselben content-String, wo Google es erwartet.

3. Vier Bilder lieferten 404 auf der englischen Startseite (_blocks/pl_comparisonSection, pl_differencesSection, pl_imageAndTextSection, pl_quoteSection)

Sechs Pfade waren relativ ohne führenden Schrägstrich eingebunden und lösten unter /en/ gegen /en/img/… auf. Jetzt durchgängig {{ alias('@web') }}/img/… — dasselbe Muster, das base.twig schon für CSS und JS verwendet.

4. Retina-Bilder waren unscharf, Startseite machte 569 statt 78 Abfragen (_components/core/image.twig, config/general.php)

Das srcset forderte dieselbe Breite dreifach an, und der 2x-Deskriptor versprach doppelte Pixeldichte, lieferte aber dasselbe Bild wie 1x — auf Retina-Displays also ein zu kleines, unscharfes Bild. Jetzt wird jede Größe einmal berechnet und 2x stimmt. Fehlende Bildgrößen erzeugt Craft wieder im Hintergrund statt im laufenden Request (Craft-Standard).

5. Kleinkram: <html lang> hatte ein doppeltes Leerzeichen, og:locale gab auf der EN-Site en statt en_US aus.

Belege (lokal gemessen, nicht behauptet)

hreflang und robots, gegen zehn Seiten (DE+EN von Startseite, Blog, Artikel, Kategorie compound-engineering, Impressum) — selbst nachgeprüft, nicht vom Agent übernommen:

/blog                → hreflang de-DE = /blog          ✔ (vorher: /)
                       hreflang en    = /en/blog       ✔
                       x-default      = /blog          ✔ (vorher: /)
/blog/category/…     → de-DE = /blog/category/…        ✔ (vorher: /)
                       en    = /en/blog/category/…     ✔ (vorher: /en/)
/impressum           → grep -c 'name="robots"' = 1     ✔ (vorher: 2)
alle Seiten          → <html lang="de-DE"> ohne Doppel-Leerzeichen ✔
                       og:locale = de_DE / en_US       ✔

Assets: 97 Bild-URLs auf DE- und EN-Startseite, alle 200, kein Bild kaputtgegangen.

Bildbezogene Datenbankabfragen: 191 → 145 (-24 %).

Was NICHT erledigt ist

Die Ladezeit ist damit nicht gelöst. Die Startseite liegt lokal weiterhin bei 0,69-0,79 s gegen 0,16 s auf /impressum — Faktor 5. Der Bild-Fix war eine von drei belegten Ursachen; die größte ist, dass es überhaupt keinen Seiten-Cache gibt (Blitz nie installiert, Crafts eingebautes Caching bewusst abgeschaltet). Jeder Aufruf macht die volle Arbeit neu, deshalb steigt die Seite unter Last auf 3-6 s. Das ist eine eigene Entscheidung und kommt nicht in diesen PR.

Die lokale TTFB-Zahl ist außerdem durch eine Dateirechte-Störung im lokalen Bild-Cache verfälscht — die echte Wirkung zeigt sich erst nach dem Deploy auf Production.

Nicht in diesem PR und separat erfasst: Sitemap mit nur 1 URL (#58), Alt-Texte, Schema.org und Fett-Tags (#59).

Nach dem Deploy zu prüfen

Die robots-Prüfung ist lokal nur eingeschränkt aussagekräftig, weil die Dev-Umgebung none, noimageindex erzwingt. Auf Production muss gelten: /impressum genau ein Tag mit noindex, Startseite genau ein Tag mit index, follow.

🤖 Generated with Claude Code

https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh

Schritt 1 von 3 gegen den Seobility-Report vom 06.09.2026 (Technik & Meta bei 8 %, -11 Punkte). Behebt die Befunde, die Sichtbarkeit kosten. `Refs #57` ## Was drin ist **1. hreflang zeigte auf Startseiten statt auf die Seite selbst** (`templates/_system/seo.twig`) `entry.localized` liefert in Craft per Definition nur die *anderen* Sites, nie die eigene — der Selbstbezug fehlte deshalb systematisch, und der Fallback in der alten Zeile 72 schickte ihn auf `site.baseUrl`. Auf entry-losen Seiten (Kategorie-Archiv, Paginierung) zeigten *alle* Verweise auf Startseiten. Jetzt: die aktuelle Site nennt sich immer selbst mit ihrer echten URL. Existiert keine Übersetzung, wird für diese Sprache **kein** Tag geschrieben statt auf die Startseite zu zeigen. `x-default` zeigt auf die deutsche Fassung derselben Seite. Funktioniert auch für Kategorie-Seiten. **2. Zwei gegenläufige robots-Tags auf den Rechtsseiten** (`templates/_system/seo.twig`) `index, follow` war hart verdrahtet und wurde zusätzlich zum Wert aus dem SEO-Feld ausgegeben — `/impressum` und `/datenschutz` lieferten beides gleichzeitig. Jetzt genau ein Tag; der SEO-Feld-Wert gewinnt, `index, follow` nur als Fallback. `unavailable_after` wandert als Direktive in denselben content-String, wo Google es erwartet. **3. Vier Bilder lieferten 404 auf der englischen Startseite** (`_blocks/pl_comparisonSection`, `pl_differencesSection`, `pl_imageAndTextSection`, `pl_quoteSection`) Sechs Pfade waren relativ ohne führenden Schrägstrich eingebunden und lösten unter `/en/` gegen `/en/img/…` auf. Jetzt durchgängig `{{ alias('@web') }}/img/…` — dasselbe Muster, das `base.twig` schon für CSS und JS verwendet. **4. Retina-Bilder waren unscharf, Startseite machte 569 statt 78 Abfragen** (`_components/core/image.twig`, `config/general.php`) Das `srcset` forderte dieselbe Breite dreifach an, und der `2x`-Deskriptor versprach doppelte Pixeldichte, lieferte aber dasselbe Bild wie `1x` — auf Retina-Displays also ein zu kleines, unscharfes Bild. Jetzt wird jede Größe einmal berechnet und `2x` stimmt. Fehlende Bildgrößen erzeugt Craft wieder im Hintergrund statt im laufenden Request (Craft-Standard). **5. Kleinkram:** `<html lang>` hatte ein doppeltes Leerzeichen, `og:locale` gab auf der EN-Site `en` statt `en_US` aus. ## Belege (lokal gemessen, nicht behauptet) hreflang und robots, gegen zehn Seiten (DE+EN von Startseite, Blog, Artikel, Kategorie `compound-engineering`, Impressum) — selbst nachgeprüft, nicht vom Agent übernommen: ``` /blog → hreflang de-DE = /blog ✔ (vorher: /) hreflang en = /en/blog ✔ x-default = /blog ✔ (vorher: /) /blog/category/… → de-DE = /blog/category/… ✔ (vorher: /) en = /en/blog/category/… ✔ (vorher: /en/) /impressum → grep -c 'name="robots"' = 1 ✔ (vorher: 2) alle Seiten → <html lang="de-DE"> ohne Doppel-Leerzeichen ✔ og:locale = de_DE / en_US ✔ ``` Assets: 97 Bild-URLs auf DE- und EN-Startseite, alle 200, kein Bild kaputtgegangen. Bildbezogene Datenbankabfragen: **191 → 145 (-24 %)**. ## Was NICHT erledigt ist **Die Ladezeit ist damit nicht gelöst.** Die Startseite liegt lokal weiterhin bei 0,69-0,79 s gegen 0,16 s auf `/impressum` — Faktor 5. Der Bild-Fix war eine von drei belegten Ursachen; die größte ist, dass es **überhaupt keinen Seiten-Cache gibt** (Blitz nie installiert, Crafts eingebautes Caching bewusst abgeschaltet). Jeder Aufruf macht die volle Arbeit neu, deshalb steigt die Seite unter Last auf 3-6 s. Das ist eine eigene Entscheidung und kommt nicht in diesen PR. Die lokale TTFB-Zahl ist außerdem durch eine Dateirechte-Störung im lokalen Bild-Cache verfälscht — die echte Wirkung zeigt sich erst nach dem Deploy auf Production. Nicht in diesem PR und separat erfasst: Sitemap mit nur 1 URL (#58), Alt-Texte, Schema.org und Fett-Tags (#59). ## Nach dem Deploy zu prüfen Die robots-Prüfung ist lokal nur eingeschränkt aussagekräftig, weil die Dev-Umgebung `none, noimageindex` erzwingt. Auf Production muss gelten: `/impressum` genau ein Tag mit `noindex`, Startseite genau ein Tag mit `index, follow`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Sechs img-Referenzen in vier Block-Komponenten (schema-bg-2/3/4.png,
pattern-1 in Varianten) waren relativ ("img/…") verlinkt. Unter der
englischen Startseite /en/ loest der Browser das gegen /en/img/… auf
und liefert 404 — unter / (DE) traf es zufaellig den echten Pfad.
Fix folgt dem im Projekt etablierten Muster aus base.twig:
{{ alias('@web') }}/img/….

Zusaetzlich role="presentation" auf dem dekorativen pattern-1-Bild
ergaenzt, alt="" bleibt korrekt bestehen.

Refs #57

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
hreflang zeigte auf der aktuellen Site nie auf sich selbst (entry.localized
liefert laut Craft-API nur die jeweils ANDEREN Sites) und fiel bei Category-
und paginierten Seiten ohne entry-Variable komplett auf die Startseite
zurück. Jetzt nennt sich jede Site mit ihrer echten aktuellen URL, fehlende
Übersetzungen erzeugen gar kein Tag mehr statt eines Startseiten-Links, und
x-default zeigt auf die deutsche Fassung derselben Seite. Generisch über
entry/category gelöst, mit siteUrl()-Fallback für elementlose Seiten.

Zusätzlich: doppeltes robots-Meta-Tag zu einem zusammengeführt (SEO-Feld
gewinnt vor "index, follow"-Fallback, unavailable_after als Direktive im
selben content-String statt eigenem Tag), doppeltes Leerzeichen in
<html lang> entfernt, und og:locale normalisiert Sprachcodes ohne Region
(en -> en_US) statt ein ungültiges Format auszugeben.

Refs #57

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Die srcset im image-Atom forderte für 1x und 2x dieselbe Breite an
(dreifacher WebP()-Aufruf pro Bild, drei DB-Lookups statt zwei
tatsächlich unterschiedlicher Größen) und lieferte Retina-Displays
damit ein zu kleines, unscharfes Bild statt der versprochenen
doppelten Auflösung. Jede Transformationsbreite wird jetzt einmalig
berechnet und wiederverwendet; 2x liefert echte doppelte Breite, und
srcset entfällt ganz, wenn keine Breite bekannt ist (verhindert
width*0-Transforms). Der tote `transforms`-Block am Dateianfang war
unbenutzt und wurde entfernt.

generateTransformsBeforePageLoad(true) ließ zusätzlich fehlende
Bild-Transforms synchron im Web-Request generieren statt sie über
eine Platzhalter-URL zu entkoppeln. Auf den Craft-Default (false)
umgestellt, da Craft dabei ohne eigenen Queue-Runner auskommt
(vendor/craftcms/cms/src/imagetransforms/ImageTransformer.php:106-118
pusht nur einen Queue-Job und liefert sofort eine temporäre
generate-transform-URL zurück).

Lokal verifiziert (general_log, gleiche Methode wie die
Vorher-Diagnose): imagetransformindex-SELECTs 191 -> 145 (-24%) bei
identischem Request. Alle 97 img-src/srcset-URLs der Startseite
(DE+EN) liefern HTTP 200.

Refs #57

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017w4iMK5VNobPwXUWXmLKKh
Phil merged commit 4ec203ce1a into main 2026-09-06 20:22:22 +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!62
No description provided.