Jede Laravel-Codebase, die ich je geerbt habe, trägt irgendwo dieselbe fossile Schicht: ein SeoTrait, das an Controller geschraubt wurde, ein Config-Array mit Default-Titeln und ein Blade-Partial voller Conditionals, das entscheidet, ob og:image auf das Site-Logo zurückfallen soll. Wir alle haben irgendeine Version davon geschrieben, und wir alle wussten insgeheim, dass das Ganze mit Klebeband zusammengehalten wird. Als Taylor Otwell auf der Laracon US 2026 Laravel Head ankündigte, war meine erste Reaktion deshalb nicht 'endlich schönere Meta-Tags'. Sondern: Endlich hat jemand das eigentliche Problem beim Namen genannt. Head-Metadaten waren nie ein Templating-Thema. Sie sind kaskadierender State — Defaults, Gruppenregeln, Routen-Werte, Request-Time-Daten — und wir haben diese Kaskade von Hand verwaltet, und zwar schlecht.
Das ist meine These, und das Design des Pakets untermauert sie. Laravel Head löst Metadaten über fünf Schichten auf, von niedrigster zu höchster Priorität: Seiten-Defaults, Routengruppen-Metadaten, Routen-Metadaten, Laufzeit-Metadaten und Fehlerseiten-Metadaten. Das entscheidende Detail: Eine höhere Schicht überschreibt nur die Felder, die sie tatsächlich setzt. Dein Controller kann Head::title($post->title) aufrufen, und die auf der Route definierte Description bleibt unangetastet. Genau diese Merge-Semantik hat dein handgestrickter SeoService mindestens einmal falsch gemacht — der Bug, bei dem eine Detailseite plötzlich den seitenweiten Default-Titel zeigt, weil jemandes Override das ganze Objekt plattgemacht hat statt eines einzelnen Feldes. Wenn du diesen Bug schon ausgeliefert hast (ich ja), verstehst du bereits, warum das ein Auflösungsproblem ist und kein Rendering-Problem.
Die Ergonomie folgt aus diesem Modell, statt gegen es anzukämpfen. Site-weite Defaults leben per Head::defaults() in einem Service Provider, statische Seiten hängen ihre Metadaten mit withHead() direkt an die Route, und eine komplette Admin-Sektion wird mit einem einzigen robots-Argument auf der Routengruppe auf noindex gesetzt — keine Middleware, kein URL-Prefix-Geschnüffel. Conditionals werden Teil der Kette: ->when($post->is_draft, fn ($head) => $head->hiddenFromRobots()) ersetzt das if-else, das du früher in einer Blade-View versteckt hast. Und die Duplizierungssteuer entfällt: og:title und og:description leiten sich aus dem Title und der Description ab, die du ohnehin setzt, Twitter Cards leiten sich aus denselben Werten ab, sobald du in deinen Defaults einen Card-Typ wie TwitterCard::SummaryWithLargeImage deklarierst, und du überschreibst nur, wenn die Social-Texte wirklich abweichen.
Der Teil, den ich unterschätzt nennen würde, ist die Cross-Stack-Geschichte. Die hässlichsten Head-Bugs, die ich gesehen habe, steckten nicht in reinem Blade — sie tauchten auf, wenn Livewires wire:navigate den vollen Page-Load übersprang und der Titel veraltet stehen blieb, oder wenn eine Inertia-App die Titel-Logik in eine React-<Head>-Komponente auslagerte, die dein PHP-Code nicht sehen konnte. Der Resolver von Laravel Head ist request-scoped, also greift Livewire-Navigation ohne jedes Extra-Verdrahten auf die Metadaten der Zielroute zu. Inertia braucht mehr Zeremonie — @head vor <x-inertia::head />, serverHead: true in createInertiaApp() (Inertia 3.5+), und wirklich statische Tags werden über Head::inertiaGlobals() registriert —, aber der Lohn ist eine einzige Source of Truth statt zweier Systeme, die um dasselbe DOM-Element ringen. Die JSON-LD-Builder sind dieselbe Idee, angewandt auf schema.org: typisierte Builder für product, breadcrumbs, faq und Co., automatisch vergebene Positionen und ein #[SchemaType]-Attribut als Fluchtluke für alles, was nicht eingebaut ist.
Jetzt das ehrliche Gegenargument, denn es ist ein gutes: Das hätte nicht First-Party sein müssen. spatie/laravel-seo existiert und wird gut gepflegt, und jedes First-Party-Paket, das Laravel absorbiert, verengt den Raum, in dem Community-Pakete gedeihen. Es hat echte Kosten, wenn das Framework eine Antwort heiligspricht — Spatie, Artesaos und ein halbes Dutzend kleinerer Autoren haben in diesem Bereich jahrelang unbezahlte Designarbeit geleistet, und 'offiziell' beendet Gespräche eher, als sie fortzuführen. Wenn dein Projekt klein ist und dein aktuelles Setup nicht wehtut, ist eine Migration allein wegen des Badges Cargo-Culting.
Ich lande trotzdem auf der First-Party-Seite, aus einem ganz bestimmten Grund: Die fünfschichtige Kaskade funktioniert nur, wenn sie sich in Routen-Definitionen, Routengruppen, Fehlerseiten und den Request-Lifecycle auf Augenhöhe einklinken kann. Ein Community-Paket erreicht diese Nahtstellen über Extension Points und Workarounds; ein First-Party-Paket bekommt Route::withHead() als nativen Bürger. Manche Probleme gehören schlicht dem Framework, weil ihre korrekte Lösung quer durch das Framework schneidet — und 'was ist der Titel dieser Response' entpuppt sich als eines davon. Die Versionsuntergrenze ist allerdings real: PHP 8.3 und Laravel 13.17 als Minimum heißt, dass viele Produktions-Apps das erst mal nicht anfassen können, und das ist okay. Der Rat aus dem Artikel selbst ist vernünftig: Migriere zuerst Title und Description, dann Open Graph, dann Schema. Niemand braucht einen 4.000-Zeilen-Pull-Request mit dem Titel 'SEO-System ersetzen'.
Eine Kleinigkeit, die ich anmerken würde, bevor du all-in gehst: Abgeleitete Metadaten sind eine zweischneidige Bequemlichkeit. Wenn og:title stillschweigend deinen Title spiegelt, ist das großartig — bis das Marketing auf zwölf Seiten abweichende Social-Texte will und sich jemand merken muss, welche Werte abgeleitet und welche explizit sind. Die alte Duplizierung war hässlich, aber wenigstens sichtbar. Ich wette, der Trade-off lohnt sich — explizite Overrides wie Head::twitter(title: $post->social_title) halten die Ausnahmen lesbar —, aber das ist die Sorte Frage, die du erst sechs Monate in einem Projekt wirklich beurteilen kannst.
Und hier ist, was ich tatsächlich von dir wissen will: Wenn du eine Head-Management-Schicht gebaut oder übernommen hast — die von Spatie, dein eigenes Trait, ein Inertia-<Head>-Setup —, bildet das fünfschichtige Modell dann ab, wie deine Metadaten wirklich fließen, oder hast du einen Fall, den es nicht ausdrücken kann? Am meisten interessieren mich Multi-Tenant-Apps, bei denen die 'Defaults' selbst pro Tenant sind und in der Datenbank liegen. Wenn du so eine hast, schreib mir unten, ob die Kaskade von Laravel Head das tragen würde — oder darunter zerbricht.
Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.