·
9 min čtení
·
Napsal Tomáš Mikeš
Migrace Laravel webu na Next.js 16 + .NET 9 — beze ztráty SEO
Pražská stomatologická klinika s dvoujazyčným Laravel webem potřebovala oslovit pacienty v šesti jazycích, a to bez ztráty pozic v Googlu. Takhle jsme to zvládli za tři týdny — a jak AI redirect matcher pokryl 97,6 % starých URL.
Zadání znělo nejdřív rutinně: úspěšná pražská stomatologická klinika si chtěla přestavět web. Jejich Laravel setup byl pět let starý, technicky v pořádku, ale limitoval byznys. Dostávali poptávky v němčině, francouzštině, ruštině a arabštině — ale web mluvil jen česky a anglicky.
Skrytým omezením bylo to, co zabije většinu migrací: nesměli jsme ztratit žádné pozice v Googlu. Pět let SEO equity na stránkách jednotlivých ošetření, které už byly na první stránce výsledků. Typický přístup „prostě to přestavíme a všechno přesměrujeme na homepage“ by je stál měsíce obnovy organické návštěvnosti.
Starý web, změřený
Než jsme napsali první řádek kódu, provedli jsme kompletní audit Laravel webu. Čísla, ne názory.
- 539 indexovaných URL napříč dvěma jazyky. To byl základ, který jsme museli zachovat — každá z nich je potenciální vstupní bod z Googlu.
- Fallback meta popisek na detailu ošetření s textem „Stránka nebyla nalezena“ — jasný bug z dávné úpravy šablony. Museli jsme si dát pozor, ať ho netáhneme s sebou.
- Canonical URL obsahující
/public/— klasický Laravel symptom, když server není nakonfigurovaný tak, aby public adresář skryl. Google byl roky zdvořile zmatený. - JSON-LD pouze na homepage. Žádná strukturovaná data na stránkách ošetření, FAQ, lékařů, klinik, ceníku — všude tam, kde se mohly objevit rich results.
- Hreflang omezený na CS+EN, s 1 054 tagy napříč 539 URL. Nekompletní a místy nesprávný.
Stack, na kterém jsme se usadili, byl pro nás záměrně nudný, ale pro klienta odvážný: Next.js 16 pro veřejný web, .NET 9 pro backend API a admin portál, PostgreSQL na Azure a Anthropic Claude pro překladovou pipeline. Celé to běží jako jediný Docker image v Azure Container Registry — jeden .NET proces hostuje API, servíruje statický React admin a přes YARP reverse proxy směruje root traffic na Next.js SSR proces.
Problém s redirecty
O úspěchu migrace rozhodovalo jedno konkrétní inženýrské rozhodnutí: jak namapovat 539 starých URL na 1 333 nových (šest jazyků × víc typů obsahu). Příliš mnoho starých URL mělo hodnotu pro byznys na to, abychom je mapovali ručně. A příliš mnoho na to, abychom je nechali na autopilotovi.
Tak jsme postavili malou AI pipeline — ne produkt, workflow:
- Načteme starý sitemap. Stáhneme každou indexovanou URL s titulkem, meta popiskem a prvním odstavcem obsahu.
- Načteme nový sitemap. To samé pro nový web, generované z naší databáze.
- Dávkové matchování přes Claude. Pro každou starou URL se modelu zeptáme, aby našel nejlépe odpovídající novou URL na základě sémantické shody obsahu. Vrací jedno z:
high-confidence / medium-confidence / no-match. - Lidská revize hraničních případů. Shody s vysokou jistotou šly rovnou do redirect tabulky. Střední vyžadovaly schválení. No-match spouštěl rozhodnutí: vytvořit obsah, nebo přesměrovat na nejbližší kategorii.
Výsledek: 97,6 % starých URL dostalo v novém systému trvalý redirect, zbývajících 2,4 % jsme ručně zkontrolovali. Žádné plošné redirecty na homepage. Žádné rozbité řetězce přenosu equity.
Zplošťování řetězců — nesexy detail, na kterém záleží
Když CMS web běží nějakou dobu, změny URL se hromadí. Článek „A“ má slug upravený na „B“, pak ho někdo edituje znovu na „C“. Naivní redirect systém vytvoří řetězec: A → B → C. Prohlížeče ho projdou, ale vyhledávače penalizují vícehopové redirecty a ztrácí PageRank na každém kroku.
Zplošťování řetězců jsme zabudovali do adminu: kdykoli se vytvoří nový redirect z X na Y, systém zkontroluje, jestli Y samo někam nepřesměrovává, a pokud ano, nasměruje X rovnou na finální cíl. Uživatel, který edituje obsah, to nikdy nevidí — ale crawler dostane vždy jedinou 308.
Ve spojení s 12měsíční retention policy (redirecty přežijí rok po změně slugu, dost času na to, aby Google reindexoval) a in-memory cache s 3minutovým TTL přidává resolving redirectu v middleware přibližně nulovou latenci k requestu.
SEO jako first-class API
Na starém webu bylo SEO něco, co šablona dělala špatně. Na novém webu jsme z něj udělali součást datové smlouvy — každá entita (ošetření, lékař, klinika, článek, kniha, FAQ) nese strukturovaná metadata, která končí jak v Next.js metadata API, tak v JSON-LD.
Nasazujeme 17 typů strukturovaných dat: MedicalOrganization spolu s LocalBusiness (duálně, protože klinika má klinický i maloobchodní provoz), Physician pro každého lékaře, MedicalProcedure pro každé ošetření, FAQPage pro Q&A, JobPosting pro kariéru, BreadcrumbList všude, Event pro akce klinik, AggregateRating tažený automaticky z importovaných Google recenzí — ten jediný rozsvítí hvězdičkové hodnocení ve výsledcích vyhledávání.
Všechno se verifikuje v Google Rich Results Test před deployem. Neprošlý JSON-LD blokuje produkční push přes malý skript v CI pipeline.
AI překladová pipeline
Se šesti cílovými jazyky byl ruční překlad mimo hru — stál by víc než zbytek projektu dohromady a vytvořil by trvalou koordinační daň pokaždé, když by editor změnil slovo.
Místo toho: každá entita má *Translation tabulku klíčovanou přes locale. Když editor uloží českou změnu, TranslationBackgroundJob zařadí dávkový požadavek do Anthropic Claude Batch API s doménově specifickým promptem (dentální terminologie, formální register, zachovat markup). Job každou minutu kontroluje stav dávky, dokud se nedokončí, a pak aplikuje překlady.
Dvě věci z toho udělaly produkční řešení, a ne hračku:
- Dashboard pro audit překladů v adminu. Editoři vidí na první pohled, které entity mají kompletní překlady a které jsou částečné nebo zastaralé. Odpadá obvyklé „počkej, ještě zkontroluju všech 6 jazyků“ při editaci obsahu.
- Glossary override. Konkrétní termíny (tituly lékařů, názvy ošetření) jsou vynucené na konkrétní překlady. Model respektuje glossary i napříč dávkami.
Po třech týdnech produkčního provozu našel audit kvality překladů méně než 0,5 % stringů, které potřebovaly editorskou korekci. Ruční překlad je pro tenhle druh obsahu mrtvý.
Co bychom udělali znovu
Tři věci, kdybychom začínali od znova:
- Nejdřív AI sitemap matching, až pak design. Redirect match jsme pouštěli v prvním týdnu. Když jsme brzy znali 2,4 % osiřelých URL, mohli jsme ten obsah navrhnout pro nový web dřív, než se z toho stala horečná práce na poslední chvíli.
- Jeden Docker image s YARP reverse proxy. Méně kontejnerů, levnější hosting, jedna CI pipeline. Pro malý až střední provoz jen přidává rozdělení frontendu a backendu do samostatných deploymentů provozní tření.
- Strukturovaná data jako smlouva. Když se na
Schema.orgtypy díváme jako na součást doménového modelu — a ne něco, co šablona přidá na poslední chvíli — odpadne celá kategorie debuggingu „proč se tohle neukazuje v Googlu“.
Co bychom zvážili znovu
Největší debata byla o tom, jestli dát český obsah do databáze s překlady, nebo mít zdroj v Markdown souborech. Šli jsme do databáze (napojené na admin UI), protože klient chtěl, aby kopii editovali i nevývojáři. Kdyby byly aktualizace obsahu méně časté, byl by Markdown + Git jednodušší.
Celkově: projekt splnil všechny tři must-have body — šest jazyků, nulová ztráta SEO a předání klientskému týmu, který dokáže editovat všechno bez vývojáře — za tři týdny a 116 commitů. Detailní čísla jsou v kompletní případové studii.
Řešíš něco podobného?
Domluvme si 30min technický call. Bez obchodních procesů — přímá architekturní zpětná vazba.