Irgendwann Ende 2022 redete ich mir ein, dass der Aufbau eines Web-Hosting-Verzeichnisses unkompliziert sein würde. Daten aggregieren, Seiten generieren, ranken, monetarisieren. Saubere Sache. Ich hatte vorher bereits programmatische SEO-Projekte gemacht – ein lokalisiertes Immobilienportal für einen UK-Kunden, eine SaaS-Vergleichsseite, die monatlich 40.000 Aufrufe erreichte – also rechnete ich damit, dass HostList ein Sechs-Wochen-Projekt würde. Es dauerte knapp sieben Monate. Und es hätte beinahe ein paar Dinge kaputtgemacht: meinen Schlafrhythmus, das Vertrauen eines meiner Junior-Entwickler und eine £180/Monat Vercel-Rechnung, die ich nicht eingeplant hatte.
Das ist die Post-Mortem-Analyse. Die echte, nicht die LinkedIn-Version.
---
Das Briefing, das ich mir selbst schrieb
HostList sollte einfach sein. Ein Verzeichnis von Web-Hosting-Anbietern – Shared Hosting, VPS, Dedicated, verwaltetes WordPress – mit einzelnen Seiten für jeden Anbieter, Vergleichsseiten, Kategorieseiten und standortbasierte Seiten (z. B. „bestes Hosting in Deutschland"). Rechne es durch: ~400 Anbieter × mehrere Seitentypen × 20+ Filterkombinationen. Du erreichst 25.000 Seiten schneller, als du denkst.
Ich habe mich für Next.js entschieden, ohne viel darüber nachzudenken. Wir nutzen es bei Seahawk für die meisten unserer größeren React-basierten Projekte. Das Ökosystem ist ausgereift, getStaticProps und getStaticPaths sind sinnvoll für SEO-lastige statische Generierung, und ich persönlich finde das dateibasierte Routing einfacher zu durchschauen als Remix oder Gatsby in diesem Maßstab.
Die erste echte Entscheidung war die Datenschicht. Ein Headless-CMS habe ich relativ schnell ausgeschlossen – ich wollte keine Contentful-Preise für 25.000 Einträge zahlen, und ich traute einem CMS nicht zu, Massen-Programmatic-Writes sauber zu handhaben. Wir landeten bei einer Postgres-Datenbank auf Supabase mit einer leichtgewichtigen Next.js-API-Schicht davor. Das hat eigentlich ganz gut funktioniert. Kompliziert wurde fast alles andere.
---
Statische Generierung im großen Maßstab: Wofür dich niemand warnt
Das Problem mit getStaticPaths und 25.000 Routes: Es funktioniert. Technisch gesehen. Aber deine Build-Zeiten werden dich deine Lebensentscheidungen überdenken lassen.
Unser erstes vollständiges Build hat 4 Stunden und 47 Minuten gedauert. Auf Vercel. Das ist genau das Zeug, das dir eine Abrechnung um 2 Uhr morgens auf den Tisch bringt, wenn du nicht vorsichtig mit deinen Plan-Limits umgekst. Ich starrte auf diese Slack-Benachrichtigung von meinem Handy und ernsthaft erwog ich, einfach WordPress zu nutzen.
Die `fallback: 'blocking'`-Falle
Mein erster Instinkt war, alles vorab zu rendern. Jede Seite, jede Kombination. Schlechte Idee, und nicht aus dem Grund, vor dem die meisten Tutorials warnen (das ist normalerweise nur „es dauert eine Weile"). Das echte Problem ist Cache-Invalidierung. Wenn ein Hosting-Anbieter seine Preise aktualisiert (und das tut er ständig), musst du betroffene Seiten neu erstellen. Wenn alles statisch vorgerendert ist ohne ISR, löst du vollständige Rebuilds für Datenänderungen aus, die vielleicht 30 Seiten von 25.000 betreffen.
Ich bin zu Incremental Static Regeneration mit einem revalidate von 86400 Sekunden (24 Stunden) für die meisten Seiten und 3600 Sekunden für preisintensive Provider-Seiten übergegangen. Das war die einzelne größte Verbesserung der Lebensqualität im ganzen Projekt. Build-Zeiten fielen unter 40 Minuten, weil wir nur die Top ~2.000 Seiten nach Traffic-Priorität vorab renderten und den Rest mit fallback: 'blocking' on-demand generieren ließen.
Das Route-Tree aufteilen
Eine Sache, die ich anders machen würde, und die ich jedem Entwickler bei Seahawk sage, der ein großes programmatisches Projekt anfasst: Teile deinen Route-Baum früh auf. Habe keine monolithische getStaticPaths-Funktion, die versucht, 25.000 Slugs zurückzugeben. Wir teilten unsere auf in:
- /providers/[slug], einzelne Anbieter-Seiten (~400)
- /compare/[slugA]-vs-[slugB], direkter Vergleich von Seiten (~8.000)
- /category/[type], Kategorie-Landingpages (~40)
- /location/[country]/[type], Geo × Kategorie-Kombinationen (~16.000+)
- /best/[use-case], kuratierte Listen-Seiten (~600)
Jede Route-Gruppe hat ihre eigene Revalidierungs-Kadenz, ihre eigene Datenbeschaffungslogik und, das ist entscheidend, ihre eigene Build-Priorität. Die Location-Seiten sind fast vollständig On-Demand. Die Provider-Seiten werden immer vorgerendert. Saubere Trennung.
---
Das Data-Pipeline-Chaos (und wie wir es behoben)
Anfang 2023 habe ich den Fehler gemacht, die Datenbeschaffungsseite von HostList.io zu locker zu bauen. Wir hatten ein Scraping-Skript (geschrieben in Python, mit BeautifulSoup und einem rotierenden Proxy-Pool von Webshare), ein manuelles Google Sheet für Korrektionen und eine Supabase-Tabelle. Drei Quellen der Wahrheit. Keine von ihnen sprach richtig miteinander.
Ein Junior Dev, guter Junge, gerade aus einem Bootcamp raus, hat drei Wochen damit verbracht, ein Sync-Skript zwischen dem Sheet und Supabase zu pflegen, das bei jeder Änderung eines Spaltennamens kaputt ging. Ich hätte das Sheet in Woche eins killen und eine richtige interne Admin UI bauen sollen. Am Ende haben wir es gemacht, mit Next.js API Routes und einem Retool Dashboard, das wir daneben angebohrt haben, aber wir haben wahrscheinlich 60 Engineering Hours dafür gebrannt.
Die Lösung: eine einzige Quelle der Wahrheit, immer. Die Datenbank ist kanonisch. Alles schreibt in die Datenbank. Die Admin-UI liest aus und schreibt in die Datenbank. Der Scraper schreibt in die Datenbank. Klingt offensichtlich. Im Nachhinein tut es das immer.
Daten in Bewegung im großen Maßstab
Für ein Verzeichnis dieser Größe ist Datenaktualität genauso ein SEO-Problem wie ein UX-Problem. Google merkt es, wenn Preistabellen £2.99/month für einen Plan anzeigen, der seit acht Monaten £5.99 kostet. Wir haben eingerichtet:
- Ein wöchentlicher Scrape-Job auf einem Railway Cron (günstig, zuverlässig, erfordert keinen dedizierten Server)
- Einen Supabase-Datenbank-Webhook, der auslöst, wenn sich eine price_updated_at-Spalte ändert, und einen Next.js-Revalidierungs-Endpunkt trifft
- Manuelle Überschreibe-Flags in Retool für die ~30 Provider, deren Websites aktiv Scraper blockieren
Dieser Revalidation Endpoint, /api/revalidate?secret=TOKEN&path=/providers/siteground, ist ein Standard Next.js Feature, aber ihn an einen Database Webhook anzudrahten hat ein bisschen Rohrleitungsarbeit gebraucht. Jede Minute wert.
---
SEO-Architektur: Was das Nadelöhr wirklich bewegte
Ich habe genug Content-Sites aufgebaut, um zu wissen, dass 25.000 Seiten nicht dasselbe ist wie 25.000 Seiten, die ranken. Die Vergleichsseiten waren die Falle. Wir haben jede mögliche A-gegen-B-Kombination für unsere ~400 Provider generiert, was uns grob 79.800 theoretische Paarungen gab. Wir haben ~8.000 davon gebaut. Und die meisten davon waren ehrlich gesagt dünn.
Ehrliche Beichte: Ich bin gierig geworden. Die SEO Logik war solide, „SiteGround vs Bluehost" hat echtes Suchvolumen, der Long-Tail von Vergleichsqueries ist riesig, aber wir haben nicht genug einzigartige Inhalte pro Seite gebaut, um die Existenz von jeder einzelnen zu rechtfertigen. Google hat angefangen, die Vergleichssektion zu crawlen und hat klar entschieden, dass es seine Zeit nicht wert ist. Googles eigene Richtlinie zu Thin Content ist deutlich darüber, und ich hätte früher deutlicher zu mir selbst sein sollen.
Was wir taten, um uns zu erholen
Wir haben ausgemistet. Die Vergleichsseiten von ~8.000 auf ~1.200 reduziert, nur Paare mit nachweisbarem Suchvolumen (verifiziert in Ahrefs, mindestens 50 monatliche Suchen weltweit). Dann haben wir die verbleibenden Seiten angereichert mit:
- Dynamische "wofür es am besten geeignet ist"-Abschnitte, die aus strukturierten Provider-Daten gezogen werden
- Echte Uptime-Daten (wir haben uns mit einer Uptime-API eines Drittanbieters integriert)
- Benutzer-Review-Zusammenfassungen aus Trustpilot-Daten, wo verfügbar
Das Ergebnis waren 1.200 Seiten, die tatsächlich nützlich waren, statt 8.000 Seiten, die es nicht waren. Der organische Traffic zum Vergleichsbereich stieg über die folgenden drei Monate um 340 %. Kontraintuitiv, bis es nicht mehr so ist.
Interne Verlinkung in diesem Maßstab
Mit 25.000 Seiten kann Internal Linking nicht manuell sein. Wir haben eine Related-Pages Komponente gebaut, die zur Build-Zeit (in getStaticProps) Supabase abfragt und die fünf relevantesten angrenzenden Seiten basierend auf Kategorie- und Standortüberschneidung zurückgibt. Keine redaktionelle Intervention nötig. Es ist nicht perfekt, gelegentlich verlinkt eine VPS Hosting Seite auf etwas ein bisschen Seitwärtiges, aber es ist zu 90% richtig, und es bedeutete, dass jede Seite vom ersten Tag an kontextuell relevante Internal Links hatte.
---
Performance: Der Teil, der dich demütigt
Man würde denken, dass Static Generation die Performance einfach machen würde. Und auf konzeptioneller Ebene tut es das auch, vorgerendertes HTML, Edge-gecacht auf Vercel's CDN, kein Server-Rendering Overhead. Aber 25.000 Seiten bedeuten 25.000 Gelegenheiten, eine schlechte Entscheidung über deinen Component Tree zu treffen.
Unser größtes Performance Problem war die Provider Vergleichstabelle. Es war eine schwere Client-side React Komponente, viel State, viel Conditional Rendering, verwendet auf Provider Seiten und Vergleichsseiten. Auf Mobile verursachte es eine Largest Contentful Paint von etwa 4,8 Sekunden. Schlecht. Wirklich schlecht für eine Site, auf der der primäre Traffic Leute sind, die mitten in einer Kaufentscheidung sind.
Wir haben sie als Server-gerenderter Static Table mit einer dünnen React Hydration Layer für die interaktiven Filter Bits neugebaut. LCP fiel auf 1,9 Sekunden. Das ist keine Magie, es ist nur, dass man die langweilige Sache richtig macht.
Das Image-Problem
Jeder Provider hat ein Logo. 400 Logos, plus Screenshots, UI Previews, Feature Icons. Wir haben den Fehler gemacht, diese in den ersten zwei Monaten auf Vercel's eingebaute Image Optimisation zu hosten. Die Bandbreitenkosten waren still und leise entsetzlich. Alles zu Cloudflare R2 mit einer Custom Domain verschoben, unsere Vercel Rechnung von £180/Monat auf £40/Monat gesenkt. Wenn du irgendetwas Image-Heavy baust, schau dir Cloudflare R2 früh an, der kostenlose Egress ist wirklich nützlich im großen Maßstab.
---
Wie die Build-Pipeline jetzt eigentlich aussieht
Für alle, die sich das konkrete Bild ansehen wollen:
- Datenerfassung, Python Scraper auf einem Railway Cron Job, schreibt zu Supabase Postgres
- Admin-Ebene, Retool-Dashboard für manuelle Bearbeitungen, Korrektionen und Provider-Flaggen
- Next.js-App, Pages Router (wir haben angefangen, bevor der App Router stabil genug war, um ihm zu vertrauen), deployed auf Vercel
- ISR + On-Demand-Revalidierung, die Top ~2.000 Seiten werden vorgebaut, der Rest On-Demand, alles mit 24-Stunden-Revalidierung
- Bilder, Cloudflare R2, über eine benutzerdefinierte Subdomain mit Cloudflare CDN davor bereitgestellt
- Analytics, Plausible für datenschutzfreundliche Traffic-Daten, Ahrefs für Ranking-Tracking
- Uptime-Überwachung, BetterUptime überwacht die fünf Traffic-intensivsten Seitentypen
Es ist nicht glamourös. Es ist auch weitgehend langweilig zu warten, was genau das ist, was man sich von einer Infrastruktur wünscht, die man drei Jahre lang laufen lässt.
---
Ehrliche Fehler, nummeriert
- Zu breit angefangen. 25.000 Seiten war immer das Ziel, aber ich hätte mit 500 hochwertigen Seiten starten und expandieren sollen. Stattdessen bin ich mit allem gestartet und hatte die ersten vier Monate ein Google-Crawl-Budget-Problem.
- Revalidation von Anfang an nicht richtig eingerichtet. Wir haben zwei Monate mit vollständigen Neubauten verschwendet, die ISR überflüssig gemacht hätte.
- Das Google Sheet behalten. Eine Single Source of Truth hätte vom ersten Tag an nicht verhandelbar sein sollen.
- Die Qualität von Vergleichsseiten unterschätzt. Volumen ist keine Strategie.
- Zu lange Vercel Image Optimization genutzt. Zu R2 sechs Wochen später gewechselt, als wir hätten sollten.
- Den Route Tree nicht früh genug aufgeteilt. Schnelle und langsame Routes in denselben getStaticPaths Call gemischt und dann gewundert, warum die Builds langsam waren.
Jede dieser Entscheidungen schien damals vernünftig zu sein. Das ist der Teil, den Tutorials nicht erfassen – schlechte Architekturentscheidungen haben normalerweise gut klingende Begründungen, wenn man sie trifft.
---
FAQ
Wie lange hat der initiale Build gedauert, um live zu gehen?
Sieben Monate vom ersten Commit bis zu einer Version, die ich komfortabel v1 nennen konnte. Die erste grobe öffentliche Version war nach etwa vier Monaten live, hatte aber ernsthafte Probleme mit dünnem Inhalt und der Vergleichsbereich war größtenteils nutzlos. Ich würde sagen vier Monate bis „technisch live" und weitere drei bis „eigentlich gut".
Würdest du den App Router verwenden, wenn du heute anfangen würdest?
Wahrscheinlich ja, für neue Projekte ab Ende 2023. Die Server Components des App Routers würden sich eigentlich gut für diese Art der datenintensiven Seitengenerierung eignen. Aber eine bestehende 25.000-Seiten-Pages-Router-App zu migrieren ist kein Projekt, das ich in absehbarer Zeit in Angriff nehme. Der Pages Router funktioniert noch, und „funktioniert" wird unterschätzt.
Wie gehst du mit Providern um, die aus dem Geschäft gehen oder ihr Angebot erheblich ändern?
Wir haben ein Status-Flag in der Datenbank: active, deprecated, redirected. Veraltete Provider bekommen eine schlichte Archivseite statt einer vollständigen Entfernung, was alle Backlinks erhält. Umgeleitete Provider (z. B. wenn ein Host einen anderen übernimmt) bekommen ein 301 über die Next.js-Redirects-Config in next.config.js. Wir überprüfen die Status-Flaggen monatlich.
Was würdest du statt Next.js verwenden, wenn du das noch mal machen würdest?
Ich weiß ehrlich gesagt nicht. Astro ist interessant für überwiegend statische Content-Seiten, und ich spiele damit bei einem kleineren Projekt herum. Aber Next.js gab uns die Flexibilität, sowohl statische als auch dynamische Sections in derselben Codebasis zu haben, das war wichtig. Für ein rein statisches Verzeichnis ohne interaktive Features könnte Astro schneller zu bauen und billiger zu betreiben sein. Frag mich in einem Jahr nochmal.
Wie stoppst du Scraper davon ab, das ganze Verzeichnis zu kopieren?
Ehrlich gesagt? Das kannst du nicht, vollständig. Wir rate-limiten die API Routes, nutzen Cloudflare's Bot Management auf dem Frontend und rotieren einen Teil der strukturierten Daten, damit gescrapte Kopien schnell veralten. Aber wenn jemand ein öffentliches Verzeichnis klonen möchte, wird er einen Weg finden. Der Burggraben ist Datenfreshness und UX-Qualität, keine technische Verschleierung.
---
Abschließender Gedanke
HostList ist kein Riesenerfolg. Es verdient Geld, Affiliate-Provisionen, ein paar direkte Werbedeals, und es rankt ganz ordentlich für vielleicht 600 der Begriffe, auf die ich ursprünglich abzielte. Das ist in Ordnung. Es war ein Lernprojekt, das nebenbei auch noch Einnahmen generiert – und das ist das Beste, was passieren kann.
Falls du darüber nachdenkst, eine großangelegte programmatische SEO-Website auf Next.js zu bauen, mein ehrlicher Rat ist dieser: Mach es. Es ist wirklich ein guter Stack für diese Aufgabe. Aber baue weniger, als du denkst, dass du brauchst, baue es besser, als du denkst, dass du Zeit für hast, und klär deine Datenarchitektur, bevor du eine einzige Page-Template schreibst.
Die Technologie ist der einfache Teil. Das ist sie immer.
