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 opisuję 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 wybrałem inne rozwiązanie. Zamiast budować warstwę API GraphQL do istniejącego WP, wyeksportowałem 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.

Aspekt Standard (WPGraphQL) Tu zastosowane
Źródło treściGraphQL API z WPStatic HTML export każdej podstrony
LayoutRekonstruowany w ReactOryginalny HTML + Cheerio transforms
Zależność od WP w runtimeWymaganaZero — WP odizolowany od użytkownika
Nowe treściPrzez GraphQL w runtimeRewalidacja przez ISR

Skala migracji — co zostało przeniesione

Typ zasobu Liczba / Rozmiar
Wpisy blogowe (artykuły)120
Strony statyczne28
Archiwa kategorii + paginacje10
Biblioteka mediów2 370 plików, 141 MB
Cache CSS (Seraphinite Accelerator)516 plików CSS
Assety wtyczek (Elementor, Cookie Law, Smush)Wszystkie w public/
XML sitemaps (Yoast standard)4 mapy + index

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.com i loading="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.

Nowa implementacja to pełny Google Consent Mode v2 z granularnym systemem zgód w 5 kategoriach (niezbędne, funkcjonalne, personalizacja, analityczne, marketingowe) i synchroniczną inicjalizacją GA4 — brak okna wait_for_update, które przy błędnej implementacji powoduje nieprawidłowy stan zgód w raportach.

Zdarzenie GA4 Trigger
scroll_depthPrzewinięcie 50% i 90% artykułu
outbound_clickKliknięcie w link zewnętrzny (sklep, social media, partnerzy)
select_contentNawigacja między podstronami portalu
search + view_search_resultsWyszukiwanie wewnętrzne na portalu
file_downloadPobranie pliku (PDF, ZIP, MP4)
shareKliknięcie ikony udostępnienia (FB, X, WhatsApp)
generate_leadWysłanie dowolnego formularza kontaktowego

Każde zdarzenie zawiera kontekst strony: page_type (article/category/author), content_category (MMA/boks/kickboxing/freak), author. Dane w GA4 nie są płaskie — można filtrować po kategorii treści i zobaczyć, które sekcje portalu angażują użytkowników najgłębiej.

FAQ Schema — 600 par pytanie/odpowiedź wygenerowanych przez AI

Google Rich Results dla FAQ (FAQPage w JSON-LD) dają potencjalnie duże zwiększenie powierzchni w SERP. Przy 120 artykułach oznacza to ogromny potencjał, ale też ogromną ilość pracy — ręczne napisanie 5 pytań do każdego artykułu to 600 par.

Claude AI przeanalizował treść każdego z 120 artykułów i wygenerował 5 par pytanie/odpowiedź zgodnych z wytycznymi Google Rich Results — pytania w naturalnym języku polskim, odpowiedzi jako pełne zdania, bez powielania treści nagłówków. Cheerio pipeline wstrzykuje sekcję FAQ i odpowiadający JSON-LD do każdego artykułu automatycznie podczas buildowania.

SEO — zachowanie bez regresji

Migracja headless to ryzyko dla SEO. Błędne przekierowania, zaginięte meta tagi, zmieniona struktura URL — każdy z tych błędów może kosztować pozycje wypracowane przez lata.

  • Wszystkie meta tagi, opisy, canonical URL, OG/Twitter cards — zachowane 1:1 z oryginalnego HTML.
  • Robots.txt zachowany bez zmian — Next.js serwuje go ze statycznego pliku.
  • Pełny zestaw XML sitemaps w standardzie Yoast SEO: post-sitemap, page-sitemap, category-sitemap, author-sitemap, sitemap_index.
  • Brak regresji w Search Console po zmianie DNS — zero 404, zero zagubionych URL-i.

Wyniki wydajnościowe

Metryka Przed (WP + Elementor) Po (Next.js + Vercel)
TTFB>600 ms (PHP, zimny cache)<200 ms (Edge CDN)
Zewnętrzne żądania do WPKażde żądanie stronyZero
JavaScript Elementora~200 KB jQuery + Elementor JS0
FAQ Schema (artykuły)Brak600 par pytanie/odpowiedź
Regresja SEOBrak — zero 404 po migracji

Wnioski — kiedy static HTML export zamiast GraphQL

Podejście z static HTML export i Cheerio pipeline nie jest standardowe — i celowo. Jest optymalne gdy frontend jest zbudowany w Elementorze (rekonstrukcja layoutu przez GraphQL byłaby trudniejsza niż transformacja istniejącego HTML), gdy chcesz całkowicie odizolować WP od użytkownika, i gdy priorytetem jest brak regresji SEO — oryginalny HTML zachowuje wszystkie meta tagi i strukturę dokładnie tak jak były.

Dla nowych projektów, gdzie frontend jest budowany od zera, standardowe podejście WPGraphQL + Apollo Client jest prostsze i łatwiejsze w utrzymaniu. Ale dla migracji istniejącego serwisu z Elementorem — static HTML export bywa jedynym sensownym wyborem.

sportywalki.com.pl po migracji ma wydajność, której współdzielony hosting z WordPressem i Elementorem nigdy nie był w stanie zapewnić — i zachowany w 100% dorobek SEO wypracowany przez lata pracy redakcyjnej.