Deine Blog-Paginierung versteckt vermutlich Beiträge vor Google

pagination

Ein Blog mit sechzig Beiträgen und zwölf pro Seite hat fünf Seiten. Seite eins zeigt zwölf Beiträge, die anderen achtundvierzig erreicht man über die Paginierung. Kann der Crawler einer Suchmaschine ihr nicht folgen, sind deine älteren Beiträge nur noch über die Sitemap erreichbar und über Links von anderen Seiten. Und für einen Beitrag, auf den nichts verlinkt, hat eine Suchmaschine wenig Grund, ihn weit oben zu zeigen.

Die meisten Blog-Paginierungen sind so gebaut, dass Crawler ihnen nicht folgen können, oder sie sagen ihnen, dass sich die Mühe nicht lohnt. Einen dieser Fehler habe ich diesen Monat im eigenen Blog gefunden, als ich ihn neu gebaut habe. Hier sind die vier häufigsten, was du stattdessen tust, und am Ende eine Checkliste.

Fehler 1: „Mehr laden“ ohne URLs

Ein „Mehr laden“-Button oder Infinite Scroll, der die nächste Seite per JavaScript nachlädt, ist angenehm zu bedienen und für Crawler unsichtbar. Laut Googles Dokumentation crawlt Google in der Regel nur die URLs im href von <a>-Elementen, die Crawler klicken keine Buttons an, und die Suche scrollt nicht und interagiert auch sonst nicht mit der Seite.

Wenn Beitrag 13 nur über einen Klick auf „Mehr laden“ erreichbar ist, verlinkt dein Blog aus Sicht eines Crawlers überhaupt nicht auf Beitrag 13.

Stattdessen: Gib jeder Seite eine URL, /blog?page=2 oder /blog/page/2, und verlinke sie mit einem echten <a href>. Ein „Mehr laden“-Button kann trotzdem obendrauf bleiben: Rendere die Paginierungslinks im HTML und lass JavaScript den Klick abfangen und die nächste Seite an Ort und Stelle anhängen. Crawler folgen dem Link, Menschen bekommen den Button.

Fehler 2: Paginierung aus Buttons

Dasselbe Problem in anderer Form:

tsx
<button onClick={() => setPage(page + 1)}>Next</button>

Der Button ändert, was die Seite zeigt, aber nicht die URL. Crawler können ihm nicht folgen, Besucher können Seite drei weder teilen noch später dorthin zurückkehren, und der Zurück-Button des Browsers springt direkt daran vorbei.

Stattdessen: Paginierung ist Navigation, also baue sie aus Links. In einem Framework mit Server-Rendering heißt das auch: Die Seite für ?page=2 wird auf dem Server gerendert, mit den Beiträgen im HTML, und genau das liest ein Crawler.

Fehler 3: Seite eins als Canonical für jede Seite

Dieser Fehler sieht nach sauberer Arbeit aus, deshalb findet man ihn überall:

HTML
<!-- auf /blog?page=2, /blog?page=3, ... -->
<link rel="canonical" href="https://example.com/blog" />

Ein Canonical-Tag sagt: „Diese Seite ist ein Duplikat, das Original liegt dort drüben.“ Seite zwei ist aber kein Duplikat von Seite eins, sie listet andere Beiträge. Behauptest du gegenüber einer Suchmaschine das Gegenteil, legst du ihr nahe, Seite zwei aus dem Index zu werfen, und mit ihr die einzigen Links auf diese Beiträge.

Googles Leitfaden zur Paginierung sagt genau das: Verwende nicht die erste Seite einer Folge als kanonische Seite, sondern gib jeder Seite ihre eigene kanonische URL.

Diesen Bug hatte ich selbst. Bis zu diesem Release hat jede Seite meines Blogs auf kookee.dev, ?page=2 eingeschlossen, /blog als Canonical angegeben. Jetzt verweist jede ungefilterte Seite per Canonical auf sich selbst: Seite eins auf /blog, Seite zwei auf /blog?page=2.

Stattdessen: ein selbstreferenzierendes Canonical auf jeder Seite der Folge.

Fehler 4: noindex ab Seite zwei

Die ältere Variante desselben Reflexes: „dünne“ Listenseiten mit noindex, follow aus dem Index halten, damit die Beiträge indexiert werden und die Listen nicht.

Der Haken ist das follow. John Mueller von Google hat gesagt, dass eine Seite, die lange genug auf noindex steht, ganz aus dem Index fällt und Google ihren Links dann nicht mehr folgt. noindex, follow wirkt am Ende also wie noindex, nofollow. Und genau diese Links führen zu deinen älteren Beiträgen.

Stattdessen: Lass die Seiten indexieren. Eine Listenseite mit zwölf Beitragstiteln und Auszügen ist kein Thin Content, und wer nach dem Thema eines Beitrags sucht, landet weit eher auf dem Beitrag als auf der Liste, die ihn verlinkt. Laut Googles Dokumentation dürfen die Seiten einer Folge außerdem denselben Titel und dieselbe Beschreibung haben, ein Problem mit doppelten Titeln gibt es also nicht.

Was ist mit Filtern?

Tags, Kategorien, Suche und Sortierung vervielfachen deine URLs: Auch /blog?tag=react&page=3&q=hooks ist eine Seite. Nicht alle davon gehören in einen Index, und ein Crawler, der jede Kombination abarbeitet, verbringt seine Zeit auf deiner Website auf die nutzloseste Art.

Zwei vernünftige Möglichkeiten:

  • Gefilterte Ansichten bekommen die ungefilterte Liste als Canonical. So mache ich es: /blog?tag=react und /blog?tag=react&page=2 geben /blog als Canonical an, während die ungefilterten Seiten auf sich selbst verweisen. Jeder Beitrag bleibt über die ungefilterte Folge erreichbar, und die Kombinationen stören nicht.

  • Filter, die ranken sollen, bekommen eigene Pfade. Ist eine Tag-Seite eine eigenständige Landingpage, dann mach sie auch zu einer: unter /blog/tag/react, mit selbstreferenzierendem Canonical und eigener Paginierung. Für alles andere bleiben Filter im Query-String.

Googles eigener Leitfaden erlaubt außerdem noindex für gefilterte und umsortierte Ansichten. Wofür du dich auch entscheidest: Wende es auf die Filter an, nicht auf die Paginierung der Hauptliste.

rel="next" und rel="prev"

Google hat 2019 bekanntgegeben, dass es sie nicht mehr nutzt. Laut Googles Dokumentation verwenden andere Suchmaschinen sie womöglich noch, aber sie ersetzen keine Links auf der Seite.

Welche Seiten du verlinkst

Eine Paginierungsleiste mit jeder Seitenzahl skaliert nicht, und eine mit nur „Zurück“ und „Weiter“ macht Seite vierzig zu einem Weg von vierzig Klicks. Der übliche Mittelweg: die erste Seite, die letzte und je eine links und rechts der aktuellen, mit Auslassungspunkten für jede Lücke.

Plain Text
‹ Zurück   1  …  6  [7]  8  …  15   Weiter ›

Über den Link auf die letzte Seite springen Menschen wie Crawler direkt zu deinen ältesten Beiträgen, und die Folge lässt sich von beiden Enden aus durchlaufen. Eine Garantie, dass ein Crawler alles erreicht, ist Paginierung trotzdem nicht. Dafür gibt es die Sitemap: Führe dort jeden Beitrag auf, mit einem lastmod vom Zeitpunkt der letzten wesentlichen Änderung.

Zwei Details zur Barrierefreiheit gehören in dieselbe Komponente:

  • Umschließe sie mit einem <nav> mit einem Label wie „Seitennavigation“ und markiere die aktuelle Seite mit aria-current="page".

  • Rendere „Zurück“ und „Weiter“ an den Enden als deaktivierten Text, nicht als Links ins Nichts oder auf die Seite, auf der man gerade ist.

Checkliste

  • Jede Seite der Liste hat ihre eigene URL, und ihre Beiträge stehen im serverseitig gerenderten HTML.

  • Die Paginierung besteht aus <a href>-Links. Ein „Mehr laden“-Button, falls du einen hast, ergänzt diese Links, statt sie zu ersetzen.

  • Jede Seite der ungefilterten Liste hat ein selbstreferenzierendes Canonical. Seite zwei verweist nicht per Canonical auf Seite eins.

  • Kein noindex auf den Seiten der Hauptliste.

  • Gefilterte und sortierte Ansichten haben die ungefilterte Liste als Canonical, stehen auf noindex oder bekommen eigene Pfade, wenn sie ranken sollen.

  • Die Sitemap führt jeden Beitrag auf.


Die React-Komponente Pagination von Kookee rendert genau dieses Muster als Links: erste und letzte Seite, je eine Seite links und rechts der aktuellen, Auslassungspunkte, eine beschriftete Navigation und deaktivierte Enden. Sie funktioniert in einer Server Component und schon bevor JavaScript geladen ist, und getPaginationItems liefert dir dasselbe Seitenfenster für eigenes Markup. Eine vollständige Listenseite mit Tag-Filtern und Suche zeigt der Artikel Einen Blog mit den React-Komponenten bauen.