Największy polski portal o sportach walki — MMA, boks, kickboxing, freak fights — działał na współdzielonym hostingu z WordPressem, Elementorem Pro i wtyczką Seraphinite Accelerator. Ponad 120 artykułów, archiwa kategorii i autorów, lata pracy redakcyjnej nagromadzone w CMS-ie. I wydajność, która nie nadążała za rosnącym ruchem.
Zadanie: zmigrować cały portal do Next.js bez żadnego downtime'u i bez regresji SEO. Poniżej opisujemy dokładnie, co zrobiliśmy, jakie podejście przyjęliśmy i dlaczego — włącznie z technicznymi decyzjami, które sprawiają, że ten projekt różni się od typowej migracji headless.
Problem: Elementor + współdzielony hosting = sufit wydajnościowy
Seraphinite Accelerator z agresywnym cachowaniem robił co mógł, ale sufit był nieunikniony. Przy każdym żądaniu z niezakeszowanego IP serwer PHP musiał wygenerować stronę od zera — Elementor, baza danych, ładowanie motywu, wszystkie wtyczki. TTFB powyżej 600 ms dla zimnego cache'u był normą, nie wyjątkiem.
Dodatkowy problem: każda zmiana designu wymagała dostępu do panelu WP i znajomości Elementora. Brak kontroli nad frontendem oznaczał brak możliwości optymalizacji pod Core Web Vitals — Elementor generuje kilkadziesiąt kilobajtów własnego JavaScriptu i CSS-u, których nie można wyłączyć bez zepsucia layoutu.
Podejście — nie GraphQL API, ale static HTML export
Większość migracji headless WordPress wygląda tak: instalujemy WPGraphQL, budujemy Next.js pobierający dane przez GraphQL, deployujemy. To standardowe podejście.
W przypadku sportywalki.com.pl wybraliśmy inne rozwiązanie. Zamiast budować warstwę API GraphQL do istniejącego WP, wyeksportowaliśmy pełny rendering każdej podstrony do statycznych plików JSON — Next.js serwuje je jako dynamiczne strony z własną warstwą transformacji HTML.
Dlaczego? Elementor generuje specyficzną strukturę HTML z własnymi klasami, inline stylami i interakcjami JS. Próba odtworzenia tego przez GraphQL wymagałaby parsowania surowych danych WP i ręcznego rekonstruowania layoutu każdej podstrony — przy 202 trasach to nierealne. Static HTML export zachowuje oryginalny rendering jeden do jednego, a transformacja Cheerio nadpisuje tylko to, co potrzebne.
Skala migracji — co zostało przeniesione
Kluczowy aspekt: wszystkie assety WordPress — CSS, JavaScript, obrazy — serwowane lokalnie z katalogu public/. Żadne żądanie przy renderowaniu strony nie trafia do serwera WP. WordPress jest całkowicie odizolowany od użytkownika końcowego.
Cheerio HTML Transform Pipeline — co robi i dlaczego jest konieczne
Wyeksportowany HTML z Elementora to nie gotowy, czysty kod. Zawiera martwy JavaScript, absolutne URL-e do starego hostingu i brakujące interakcje. Własny parser Cheerio przetwarza każdy wyeksportowany plik i wykonuje serię transformacji:
- Usuwanie martwego JavaScriptu — skrypty Elementora, jQuery i wszystkie inline skrypty niepotrzebne po odizolowaniu od WP.
- Normalizacja URL-i — wszystkie absolutne linki do starego hostingu zamieniane na relatywne lub nowe URL-e Vercel.
- Iniekcja sekcji FAQ — do każdego artykułu dodawana jest sekcja FAQ z 5 pytaniami wygenerowanymi przez AI. Łącznie 600 par pytanie/odpowiedź dla 120 artykułów.
- YouTube embeds — 10 filmów przypisanych do konkretnych artykułów osadzanych w odpowiednich miejscach, z
youtube-nocookie.comiloading="lazy". - Dynamiczny TOC — nagłówki H2/H3 wyekstrahowane, budowany spis treści dla każdego artykułu.
- Asset proxy fallback — trasy
/wp-assets/[...assetPath]jako fallback dla dynamicznych URL-i Elementora.
Elementor bez Elementora — interakcje w czystym TypeScript
Popupy scroll-triggered, off-canvas panele, FAQ akordeony — wszystkie działały przez JavaScript Elementora, który wymagał jQuery. Łącznie ponad 200 KB JavaScriptu zbędnego w nowej architekturze.
Każda interakcja odtworzona od zera w czystym TypeScript: popupy przez własny hook z IntersectionObserver, off-canvas panel jako komponent React z animacją CSS, FAQ akordeony jako prosty useState — kilkanaście linii kodu zamiast 200 KB zewnętrznej biblioteki.
Efekt: 200 KB mniej JavaScriptu po stronie użytkownika, bezpośrednie przełożenie na LCP i TTI.


