Jak przeniosłem DIGIKROM z WordPressa na własny Static Hybrid bez zmiany adresów URL — Case Study
W tym artykule
Przez długi czas digikrom.pl działał na WordPressie. Woodmart, Elementor, własne wtyczki, własne renderery, rozbudowane menu, portfolio, katalog autorskich modułów, Wiedza, Słownik, formularze, analityka i dziesiątki elementów, które narastały wraz z serwisem.
Dało się to optymalizować.
I robiłem to.
Usuwałem zbędne zasoby, zmieniałem kolejność ładowania skryptów, wycinałem CSS, przebudowywałem hero, tworzyłem własne lekkie renderery i eliminowałem kolejne zależności.
Efekty momentami były bardzo dobre. Stronę główną udało mi się doprowadzić nawet do wyników PageSpeed bliskich 99/100.
Tyle że w pewnym momencie zadałem sobie ważniejsze pytanie:
Czy naprawdę chcę przez kolejne lata optymalizować system, którego publiczny frontend z definicji ładuje znacznie więcej, niż potrzebuję?
Odpowiedź była prosta.
Nie.
Punkt wyjścia: WordPress potrafił być naprawdę ciężki
Zachowany pomiar PageSpeed Insights z 7 sierpnia 2026 dla mobilnej wersji digikrom.pl pokazywał:
Performance: 62
Accessibility: 86
Best Practices: 100
SEO: 100
FCP: 4,1 s
LCP: 7,9 s
TBT: 70 ms
CLS: 0
Speed Index: 6,1 s
Nie był to jednak wyłącznie problem jednego wolnego obrazka czy jednego skryptu.
Audyt pokazywał typową konsekwencję rozbudowanego WordPressa: dużą liczbę arkuszy CSS i plików JavaScript pochodzących z motywu, Elementora, WooCommerce, systemu cookies i własnych rozszerzeń.
Mogłem dalej wyłączać kolejne elementy.
Zamiast tego postanowiłem zmienić reguły gry.
Nie chciałem usuwać WordPressa
To była jedna z najważniejszych decyzji całego projektu.
WordPress nie był problemem jako CMS czy zaplecze dla istniejących elementów. Problemem była konieczność używania go do renderowania każdej publicznej strony.
Dlatego nie zrobiłem klasycznej migracji:
WordPress → nowy system → wyłączenie WordPressa.
Zbudowałem hybrydę.
Jej podstawowa zasada jest banalnie prosta:
STATIC IF EXISTS
→ OTHERWISE WORDPRESS
Jeżeli dla danego adresu istnieje wersja statyczna, serwer zwraca ją natychmiast.
Jeżeli jej nie ma, żądanie trafia do WordPressa tak jak wcześniej.
Dzięki temu oba systemy mogą działać jednocześnie pod jedną domeną.
Bez zmiany istniejących URL-i
Nie chciałem tworzyć nowej struktury adresów tylko dlatego, że zmieniła się technologia.
Jeżeli artykuł wcześniej znajdował się pod:
/co-to-jest-wordpress/
to po migracji nadal znajduje się dokładnie tam.
Nie pod:
/wiedza/co-to-jest-wordpress/
Nie pod:
/articles/co-to-jest-wordpress/
Nie interesuje mnie, jakiego typu obiektem jest wpis w bazie.
Publiczny adres jest kontraktem.
Dlatego w Static Hybrid jedynym źródłem prawdy dla routingu jest ścieżka URL.
To pozwoliło mi zachować istniejącą indeksację, linkowanie wewnętrzne, backlinki i historię adresów.
Powstał własny CMS
Dość szybko stało się jasne, że samo pisanie statycznych plików nie wystarczy.
Potrzebowałem normalnego środowiska pracy.
Powstał więc własny panel administracyjny obsługujący między innymi:
- treści,
- wersje robocze,
- rewizje,
- preview,
- publikację,
- obrazy wyróżniające,
- bibliotekę mediów,
- kategorie,
- menu,
- Site Shell,
- SEO,
- przekierowania,
- import z WordPressa,
- Wiedzę,
- Słownik,
- Portfolio,
- katalog autorskich wtyczek i modułów.
Najważniejsze było jednak to, że edycja treści i publiczny frontend zostały od siebie oddzielone.
CMS może robić dużo. Publiczna strona ma robić bardzo mało.
Staging zamiast edycji na żywym organizmie
Cała praca odbywa się na osobnym środowisku stagingowym:
xxxxxxxx.pl (adres oczywiście ukryty)
Staging ma twardą blokadę indeksacji.
Nie tylko przez meta robots.
Dodałem niezależne zabezpieczenia na poziomie aplikacji i serwera, dlatego przypadkowa publikacja stagingu w Google nie zależy od jednego znacznika HTML.
Dopiero po sprawdzeniu zmian tworzona jest specjalna wersja produkcyjna z canonicalami wskazującymi digikrom.pl.
227 adresów i SEO Parity
Jednym z największych ryzyk migracji nie była technologia.
Było nim SEO.
Dlatego powstał własny SEO Parity Gate.
System kontroluje wszystkie trasy objęte migracją.
Finalny gate przed produkcją wyglądał tak:
TOTAL: 227
OK: 227
REVIEW: 0
ERROR: 0
FETCH_ERROR: 0
MIGRATION_LOCK: ON
LOCK_ROUTES: 227
Sprawdzane są między innymi:
- status HTTP,
- canonical,
- robots,
- obecność w sitemapie,
- metadane,
- routing,
- referencje do stagingu,
- elementy Schema.
Nie chciałem sytuacji, w której „strona wygląda dobrze”, ale kilkadziesiąt adresów po cichu wypada z indeksu.
Sitemap nie musi mieć tyle samo adresów co routing
Migration Lock obejmuje 227 tras.
Sitemap produkcyjna zawiera ich 219.
To nie jest błąd.
Osiem adresów to kolejne strony paginacji Wiedzy i Słownika. Mają celowo:
noindex,follow
i dlatego nie są publikowane w sitemapie.
To drobny przykład, ale dobrze pokazuje różnicę pomiędzy kopiowaniem starej strony a świadomym projektowaniem jej semantyki.
Schema prawie zgubiłem po drodze
Po pierwszym uruchomieniu Static zauważyłem coś niepokojącego.
HTML był szybki.
SEO techniczne się zgadzało.
Ale dane strukturalne były znacznie uboższe niż w moim wcześniejszym rozwiązaniu WordPressowym.
Roboczo nazwałem to Schema Bida Edition.
Okazało się, że importer starej konfiguracji nie przeniósł pełnej warstwy semantycznej.
Naprawiłem importer i rozszerzyłem renderery.
Przykładowa strona usługi może dziś generować graph zawierający:
ProfessionalService
WebSite
WebPage
Service
FAQPage
BreadcrumbList
Do tego dochodzą między innymi:
- provider,
- areaServed,
- audience,
- offers,
- PriceSpecification,
- dane kontaktowe,
- adres,
- logo,
- obrazy.
Nie chciałem, aby szybszy frontend oznaczał uboższą informację dla Google i systemów AI.
Static nie oznacza martwego HTML
Część treści DIGIKROM zawiera własne interaktywne komponenty.
Dlatego do Static przeniosłem również między innymi:
- kalkulatory,
- generatory,
- miniaplikacje,
- interaktywne demonstracje,
- formularz kontaktowy,
- portfolio,
- katalog modułów.
Elementy, które wcześniej działały jako shortcode WordPressa, stopniowo stały się natywnymi komponentami Static Hybrid.
To ważna różnica.
Nie próbowałem zamienić aplikacji internetowej w zbiór nieruchomych dokumentów.
Przeniosłem logikę tam, gdzie była potrzebna, a usunąłem runtime tam, gdzie nie wnosił żadnej wartości.
Publikacja bez SSH
Początkowo deployment wyglądał klasycznie.
SSH.
Build.
Kopiowanie.
Sprawdzenie.
Zmiana symlinka.
Smoke test.
Rollback w pogotowiu.
Działało, ale jeżeli własny CMS wymaga ode mnie działania w terminalu SSH przy każdej publikacji, to nie jest jeszcze skończony CMS.
Dlatego powstał Production Release Pipeline.
Obecny workflow wygląda tak:
EDYCJA
↓
XXXXX / STAGING
↓
PRZYGOTUJ PRODUKCJĘ
↓
PRODUCTION CANDIDATE
↓
SEO / MANIFEST / MIGRATION GATE
↓
DEPLOY
↓
ATOMIC SWITCH
↓
SMOKE TEST
↓
PRODUCTION
Jeżeli smoke test wykryje krytyczny problem, system może wrócić do poprzedniego immutable buildu.
Shared hosting powiedział „nie”
Tutaj projekt zrobił się szczególnie ciekawy.
Staging i produkcja działają na hostingu współdzielonym.
PHP stagingu nie mógł uzyskać dostępu do katalogu runtime produkcji przez ograniczenie open_basedir.
Na własnym VPS-ie rozwiązaniem byłaby zmiana konfiguracji.
Na hostingu współdzielonym nie miałem takiej możliwości.
Nie chciałem również budować rozwiązania zależnego od ręcznego SSH.
Powstał więc Production Bridge.
Schemat wygląda tak:
xxxxxxxx.pl/admin
↓
podpisane żądanie HTTPS
↓
HMAC-SHA256
↓
Production Bridge
↓
lokalny runtime produkcji
↓
atomic deploy / rollback
Bridge znajduje się po stronie produkcyjnej, dlatego ma lokalny dostęp do runtime.
Staging nie próbuje obchodzić open_basedir.
Komunikuje się z produkcją przez podpisany kanał HTTPS.
To rozwiązanie pozwoliło zachować ograniczenia hostingu zamiast z nimi walczyć.
Pierwszy deploy jednym przyciskiem
Moment, w którym system naprawdę przestał być eksperymentem, wyglądał tak:
STAGING ACTIVE #274
PRODUCTION ACTIVE #272
PENDING CANDIDATE #273
GATE READY
Kliknąłem deploy.
Po chwili:
STAGING ACTIVE #274
PRODUCTION ACTIVE #273
ROLLBACK #272
Bez SSH.
Bez kopiowania katalogów.
Bez ręcznej zmiany symlinka.
I wtedy wiedziałem, że Static Hybrid stał się realnym systemem produkcyjnym.
GTM i Consent Mode też musiały przeżyć migrację
Po uruchomieniu nowej warstwy publicznej odkryłem jeszcze jeden brak.
Google Tag Manager.
WordPress wcześniej uruchamiał kontener
a wewnątrz niego znajdowała się analityka i konwersje.
Nie chciałem osadzać GA4 osobno w Static, ponieważ stworzyłbym dwa źródła konfiguracji.
Dlatego źródłem prawdy pozostał GTM.
Powstała natywna warstwa Analytics / Consent Layer obsługująca:
- Google Tag Manager,
- Consent Mode v2,
- analytics_storage,
- ad_storage,
- ad_user_data,
- ad_personalization,
- wybór zgód,
- ich zapis,
- ponowne otwieranie ustawień.
Na stagingu GTM w ogóle się nie uruchamia.
Na produkcji zgody są ustawiane jeszcze przed GTM.
Najbardziej zdradliwy problem: Consent stał się LCP
Optymalizacja mobile przyniosła ciekawy przypadek.
Sam frontend zaczął być bardzo szybki.
TTFB często wynosił zaledwie kilka lub kilkanaście milisekund, FCP schodził w okolice jednej sekundy, TBT pozostawał niski, a CLS wynosił 0.
A mimo to Lighthouse nadal potrafił pokazywać LCP przekraczający kilka sekund.
Dlaczego?
Baner zgody był początkowo tworzony przez JavaScript po wyrenderowaniu dokumentu.
Strona pojawiała się szybko.
Później pojawiał się duży panel Consent.
Lighthouse stwierdzał:
„To jest teraz największy element.”
I przesuwał LCP.
Rozwiązaniem nie było dalsze kompresowanie hero.
Consent został przeniesiony do initial HTML, a jego stan jest ustalany przed pierwszym paintem.
To jeden z tych przypadków, w których wynik wydajności poprawia się nie przez szybszy serwer, tylko przez zrozumienie sposobu, w jaki przeglądarka wyznacza metrykę.
Critical CSS zamiast kolejnych requestów
Kolejne pomiary pokazały, że obrazy LCP potrafią zostać pobrane w kilkaset milisekund, ale przeglądarka nadal czekała z ich pokazaniem.
Powodem była ścieżka krytyczna CSS.
Zamiast dalej manipulować preloadem zacząłem usuwać z niej same requesty.
Najważniejsze style rendererów zostały osadzone bezpośrednio w wygenerowanym HTML.
Po tej zmianie przykładowe obrazy hero osiągały w testach wartości rzędu:
TTFB: 0–20 ms
Resource load delay: około 100–160 ms
Resource load: około 30–230 ms
Render delay: około 70–140 ms
To już nie przypominało ciężkiego frontendu WordPressa.
PRZED i PO
Historyczny WordPress
Mobile Performance: 62
FCP: 4,1 s
LCP: 7,9 s
TBT: 70 ms
CLS: 0
Speed Index: 6,1 s
Static Hybrid
Na obecnym etapie warstwa techniczna osiąga w testach między innymi:
TTFB: 0–20 ms
FCP: około 0,9 s
TBT: kilkadziesiąt ms
CLS: 0
SEO: 100
Best Practices: 100
SEO Parity: 227 / 227
Finalny wynik Mobile Performance i LCP uzupełnię po zakończeniu ostatniej rundy optymalizacji Consent i cold-start.
Nie chcę wpisywać wartości 100 tylko dlatego, że dobrze wyglądałaby w Case Study.
Wolę opublikować rzeczywisty, powtarzalny wynik.
Najważniejszy efekt nie mieści się w PageSpeed
Ostatecznie największą korzyścią nie jest różnica pomiędzy 62, 89, 95 czy 100 punktów.
Jest nią kontrola.
Dzisiaj wiem dokładnie:
- jaki HTML trafi na produkcję,
- jakie style są krytyczne,
- jakie skrypty się uruchamiają,
- która strona jest statyczna,
- która nadal trafia do WordPressa,
- jakie Schema generuje renderer,
- jakie adresy mają być indeksowane,
- jaka wersja jest aktywna,
- do jakiego buildu mogę wrócić.
Nie optymalizuję już zachowania kilkunastu niezależnych warstw.
Projektuję cały request od początku do końca.
Czy WordPress był błędem?
Nie.
I to jest dla mnie ważny wniosek.
WordPress pozwolił zbudować wcześniejsze wersje serwisu, zarządzać treściami i szybko rozwijać funkcjonalności.
Nie muszę również wyrzucać go tylko dlatego, że stworzyłem własny frontend.
W Static Hybrid WordPress nadal obsługuje te obszary, dla których ma sens.
Po prostu przestał być obowiązkowym rendererem każdej publicznej strony.
Czy zrobiłbym to dla każdej strony?
Nie.
Dla małej strony firmowej z kilkunastoma podstronami takie rozwiązanie byłoby przerostem formy nad treścią.
Ale gdy serwis jest jednocześnie:
- stroną firmy,
- bazą wiedzy,
- portfolio,
- katalogiem aplikacji,
- laboratorium własnych komponentów,
- platformą SEO,
- miejscem testowania nowych technologii,
to kontrola nad architekturą zaczyna mieć realną wartość.
W moim przypadku miała.
Jak bym to podsumował?
DIGIKROM Static Hybrid nie powstał dlatego, że chciałem „uciec z WordPressa”.
Powstał dlatego, że chciałem rozdzielić trzy rzeczy, które wcześniej były ze sobą zbyt mocno związane:
zarządzanie treścią, logikę aplikacji i publiczny rendering.
Dziś mogę opublikować nową statyczną wersję istniejącego adresu, zachować WordPress jako fallback, przeprowadzić automatyczny SEO gate, wdrożyć immutable build, wykonać smoke test i w razie potrzeby wrócić do poprzedniej wersji.
- Bez zmiany publicznego URL.
- Bez ręcznego deploymentu.
- Bez wyłączania WordPressa.
I właśnie o to chodziło.
