·
9 Min Lesezeit
·
Geschrieben von Tomáš Mikeš
Migration einer Laravel-Website zu Next.js 16 + .NET 9 — ohne SEO-Verlust
Eine Zahnklinik mit einer zweisprachigen Laravel-Website wollte Patienten in sechs Sprachen erreichen, ohne Google-Rankings zu verlieren. So haben wir es in drei Wochen geschafft — und wie der Redirect-KI-Matcher 97,6 % der alten URLs abgedeckt hat.
Der Auftrag klang zunächst nach Routine: Eine erfolgreiche Prager Zahnklinik wollte ihre Website neu aufbauen. Ihr Laravel-Setup war fünf Jahre alt, technisch in Ordnung, aber eine Bremse für das Geschäft. Sie erhielten Anfragen auf Deutsch, Französisch, Russisch und Arabisch — aber die Website sprach nur Tschechisch und Englisch.
Die versteckte Einschränkung war diejenige, die die meisten Migrationen killt: Wir durften keine Google-Rankings verlieren. Sie hatten fünf Jahre SEO-Equity auf behandlungsspezifischen Landingpages aufgebaut, die bereits auf Seite eins rankten. Der typische Ansatz „wir bauen einfach alles neu und leiten alles auf die Startseite um“ hätte sie Monate an Erholung des organischen Traffics gekostet.
Die alte Website, vermessen
Bevor wir eine einzige Codezeile geschrieben haben, haben wir ein vollständiges Audit der Laravel-Website durchgeführt. Zahlen, keine Meinungen.
- 539 indexierte URLs über zwei Locales. Das war die Basis, die wir erhalten mussten — jede einzelne ein potenzieller Einstiegspunkt von Google.
- Fallback-Meta-Beschreibung auf Behandlungsdetailseiten mit dem Text „Stránka nebyla nalezena“ („Seite nicht gefunden“ auf Tschechisch). Ein klarer Bug aus einem längst vergangenen Template-Tweak — aber einer, den wir nicht mitnehmen durften.
- Canonical-URLs, die
/public/enthielten — ein klassisches Laravel-Symptom, wenn der Server nicht so konfiguriert ist, dass er das public-Verzeichnis verbirgt. Google war jahrelang höflich verwirrt. - JSON-LD nur auf der Startseite. Keine strukturierten Daten auf Behandlungsseiten, FAQ, Ärzten, Kliniken, Preisen — überall dort, wo Rich Results hätten erscheinen können.
- Hreflang auf CS+EN beschränkt, mit 1.054 Tags über 539 URLs. Unvollständig und an einigen Stellen falsch.
Der Stack, auf den wir uns festgelegt haben, war für uns bewusst langweilig, für den Kunden aber mutig: Next.js 16 für die öffentliche Website, .NET 9 für die Backend-API und das Admin-Portal, PostgreSQL auf Azure und Anthropic Claude für die Übersetzungs-Pipeline. Das Ganze wird als ein einziges Docker-Image in der Azure Container Registry ausgeliefert — ein .NET-Prozess hostet die API, liefert das statische React-Admin aus und routet über einen YARP Reverse Proxy den Root-Traffic zum Next.js-SSR-Prozess.
Das Redirect-Problem
Der Erfolg der Migration würde von einer bestimmten Engineering-Entscheidung abhängen: wie wir 539 alte URLs auf die 1.333 neuen (sechs Sprachen × mehr Content-Typen) mappen würden. Zu viele dieser alten URLs waren umsatzrelevant, um sie manuell zu mappen. Zu viele, um sie dem Autopiloten zu überlassen.
Also haben wir eine kleine KI-Pipeline gebaut — kein Produkt, sondern einen Workflow:
- Alte Sitemap einlesen. Jede indexierte URL mit ihrem Titel, der Meta-Beschreibung und dem ersten Absatz des Inhalts abrufen.
- Neue Sitemap einlesen. Dasselbe für die neue Website, generiert aus unserer Datenbank.
- Batch-Matching mit Claude. Für jede alte URL das Modell bitten, die am besten passende neue URL auf Basis der semantischen Überlappung des Inhalts zu finden. Rückgabe eines der folgenden Werte:
high-confidence / medium-confidence / no-match. - Edge-Cases durch Menschen prüfen. High-Confidence- Treffer landeten direkt in der Redirect-Tabelle. Medium-Treffer erforderten eine Freigabe. No-Match löste eine Entscheidung aus: Content erstellen oder auf die nächstgelegene Kategorieseite weiterleiten.
Ergebnis: 97,6 % der alten URLs haben im neuen System einen permanenten Redirect erhalten, die verbleibenden 2,4 % wurden manuell geprüft. Keine Pauschal-Redirects auf die Startseite. Keine gebrochenen Equity-Transfer-Ketten.
Ketten-Flattening — das unspektakuläre Detail, das zählt
Wenn eine CMS-basierte Website eine Weile läuft, sammeln sich URL-Änderungen an. Der Slug von Artikel „A“ wird auf „B“ geändert, dann editiert ihn jemand erneut zu „C“. Ein naives Redirect-System erzeugt eine Kette: A → B → C. Browser folgen ihr, aber Suchmaschinen bestrafen Multi-Hop-Redirects und verlieren bei jedem Schritt PageRank.
Wir haben Ketten-Flattening ins Admin eingebaut: Immer wenn ein neuer Redirect von X nach Y erstellt wird, prüft das System, ob Y selbst schon irgendwohin weiterleitet, und zeigt dann X direkt auf das endgültige Ziel. Der Content-Redakteur sieht davon nichts — aber der Crawler bekommt immer einen einzigen 308.
In Kombination mit einer 12-Monats-Retention-Policy (Redirects bleiben ein Jahr nach der Slug-Änderung bestehen, genug Zeit für Google zur Reindexierung) und In-Memory-Caching mit einer TTL von 3 Minuten fügt die Redirect-Auflösung in der Middleware praktisch keine Latenz zum Request hinzu.
SEO als First-Class-API
Auf der alten Website war SEO etwas, das das Template schlecht gemacht hat. Auf der neuen Website haben wir es zum Teil des Datenvertrags gemacht — jede Entität (Behandlung, Arzt, Klinik, Artikel, Buch, FAQ) trägt strukturierte Metadaten, die sowohl in der Next.js-Metadata-API als auch in JSON-LD landen.
Wir liefern 17 Arten strukturierter Daten aus: MedicalOrganization zusammen mit LocalBusiness (beides, weil die Klinik sowohl klinische als auch Retail-Operationen betreibt), Physician für jeden Arzt, MedicalProcedure für jede Behandlung, FAQPage für Fragen und Antworten, JobPosting für Karriere, BreadcrumbList überall, Event für Klinik-Events, AggregateRating automatisch aus importierten Google-Bewertungen gezogen — das allein bringt Stern-Ratings in den Suchergebnissen zum Leuchten.
Alles wird vor dem Deploy in Googles Rich Results Test verifiziert. Ein fehlerhaftes JSON-LD blockiert den Produktions-Push über ein kleines Skript in der CI-Pipeline.
Die KI-Übersetzungs-Pipeline
Bei sechs Zielsprachen war manuelle Übersetzung keine Option — sie hätte mehr gekostet als der Rest des Projekts zusammen und eine permanente Koordinierungs-Steuer verursacht, jedes Mal wenn der Redakteur ein Wort ändert.
Stattdessen: Jede Entität hat eine *Translation-Tabelle mit Locale als Schlüssel. Wenn der Redakteur eine tschechische Änderung speichert, stellt ein TranslationBackgroundJob eine Batch-Anfrage an die Anthropic Claude Batch API in die Warteschlange — mit einem domänenspezifischen Prompt (Zahnmedizin-Terminologie, formale Anrede, Markup erhalten). Der Job pollt den Batch jede Minute, bis er fertig ist, und wendet dann die Übersetzungen an.
Zwei Details haben daraus etwas Produktionsreifes gemacht statt eines Spielzeugs:
- Translation-Audit-Dashboard im Admin. Redakteure sehen auf einen Blick, welche Entitäten vollständige Übersetzungen haben und welche unvollständig oder veraltet sind. Das macht den üblichen Schritt „lass mich kurz alle 6 Sprachen prüfen“ beim Content-Editing überflüssig.
- Glossar-Override. Bestimmte Begriffe (Arzttitel, Behandlungsnamen) werden auf bestimmte Übersetzungen festgelegt. Das Modell respektiert das Glossar auch über Batches hinweg.
Nach drei Wochen produktivem Einsatz ergab das Qualitätsaudit der Übersetzungen, dass weniger als 0,5 % der Strings eine Redakteurs-Korrektur benötigten. Manuelle Übersetzung ist für diese Art von Content tot.
Was wir wieder tun würden
Drei Dinge, falls wir noch einmal von vorn anfangen würden:
- KI-Sitemap-Matching zuerst, Design danach. Wir haben das Redirect-Matching in Woche eins durchgeführt. Früh zu wissen, welche 2,4 % der URLs verwaist waren, hat uns erlaubt, diese Content-Stücke für die neue Website zu gestalten, bevor sie zu einem Eilauftrag wurden.
- Ein einziges Docker-Image mit YARP Reverse Proxy. Weniger Container, günstigeres Hosting, eine CI-Pipeline. Für kleine bis mittlere Traffic-Volumen fügt das Aufsplitten von Frontend und Backend in separate Deployments nur operative Reibung hinzu.
- Strukturierte Daten als Vertrag.
Schema.org-Typen als Teil des Domain-Modells zu behandeln — nicht als etwas, was das Template in letzter Minute hinzufügt — eliminiert eine ganze Kategorie von „warum taucht das nicht in Google auf“-Debugging.
Was wir überdenken würden
Die größte Debatte drehte sich darum, ob wir die tschechischen Inhalte mit Übersetzungen in die Datenbank legen oder die Quelle in Markdown-Dateien ablegen sollten. Wir haben uns für die Datenbank entschieden (an die Admin-UI gebunden), weil der Kunde wollte, dass Nicht-Entwickler die Texte bearbeiten können. Wären die Content-Updates seltener gewesen, wäre Markdown + Git einfacher gewesen.
Insgesamt: Das Projekt hat alle drei Must-haves getroffen — sechs Sprachen, null SEO-Verlust und eine Übergabe an ein Kundenteam, das alles ohne Entwickler bearbeiten kann — innerhalb von 3 Wochen und 116 Commits. Die detaillierten Zahlen finden Sie in der vollständigen Case Study.
Arbeiten Sie an etwas Ähnlichem?
Vereinbaren Sie ein 30-minütiges technisches Gespräch. Kein Vertriebsprozess — direktes architektonisches Feedback.