Blog GSMX przeszedł z WordPressa na statyczne Astro i dla czytelnika zmieniła się tylko szybkość. Adresy, wpisy i reklamy są te same, a forum obok działa jak wcześniej. Sprawdziłem 2299 starych adresów i żaden nie prowadzi w złe miejsce. Na telefonie PageSpeed Insights wzrósł z 33–85 do 99–100 (mediana z 3 przebiegów), a serwer odpowiada w niecałe 60 ms zamiast ponad sekundy.
W skrócie:
- Ten sam adres. Blog został pod
gsmx.pl/blog/. Serwuje go Cloudflare Worker przypięty tylko do tej ścieżki, więc forum nie zauważyło zmiany. - Żaden adres nie zginął. Wpisy zachowały slugi 1:1. Zmieniło się tylko to, co było błędem, i każda taka zmiana ma przekierowanie 301.
- Pomiar przed i po tą samą metodą. Te same 5 adresów, ten sam Lighthouse i mediana z 3 przebiegów. Bez tego porównanie nic nie znaczy.
- Reklamy zostały. To one są teraz najcięższą częścią strony, nie blog.
- Powrót był zawsze o jedną komendę. WordPress został na hostingu na 4 tygodnie obserwacji.
Punkt wyjścia: WordPress obok forum
gsmx.pl to przede wszystkim forum, które działa w katalogu głównym domeny. Blog z newsami GSM siedział w katalogu /blog/, na osobnym WordPressie, z wpisami od 2006 roku.
Stan z 25 października 2026, z produkcji i z kopii bazy:
- 26 zainstalowanych wtyczek, z czego 20 aktywnych: Jetpack, Rank Math, Quick AdSense Reloaded, WP Hide, Fast Velocity Minify, Sucuri, Disqus Conditional Load, auto-linker SEO i inne. Do tego motyw Presso z motywem potomnym i trzy zestawy tabel w bazie po starych instalacjach.
- 33–42 skrypty i 8–10 arkuszy CSS na stronę. HTML szedł przez Cloudflare bez cache, a serwer odpowiadał w 0,8–1,6 s.
- Wpisy ważyły 14–17 MiB według PageSpeed Insights. Większość tej wagi to reklamy wideo ładowane na każdym wpisie.
- Treść z 20 lat: kilkaset wpisów, 705 tagów, osadzenia Flash, obrazki z tinypic i fotosik, które już nie istnieją, galerie, tabele i style wpisane w HTML.
Ruch z Google był znikomy: 16 kliknięć w 16 miesięcy. To ważne dla oceny ryzyka. Migracja nie mogła popsuć SEO, którego prawie nie było, i nie obiecuję tu wzrostu ruchu. Powody były inne. Chciałem strony bez PHP i bazy, czyli bez niczego, co trzeba łatać. Chciałem, żeby dało się ją przenieść na dowolny hosting plików. I chciałem publikować nowe wpisy z repozytorium, bez logowania do panelu.
Najpierw pomiar „przed”
Zanim cokolwiek zmieniłem, zmierzyłem produkcję. Pomiar po migracji ma sens tylko wtedy, gdy powtarzasz dokładnie to samo, więc metodę ustaliłem na początku i nie zmieniałem jej do końca:
- 5 adresów: strona główna bloga, kategoria i trzy wpisy z lat 2026, 2010 i 2006. Stare wpisy mają najwięcej starego HTML-a.
- Lighthouse 12 lokalnie, 3 przebiegi na adres i tryb, mediana. Jeden przebieg kłamie. W PageSpeed Insights dwa wpisy z niemal identycznym HTML-em dostały 85 i 33.
- PageSpeed Insights i GTmetrix jako publiczne, sprawdzalne zrzuty.
- Pełna lista adresów z REST API WordPressa: wpisy, strony, kategorie, tagi, autorzy, media. Z tej listy powstał później test przekierowań.

Jednej rzeczy nie dało się zmierzyć. Google nie miał dla bloga danych od prawdziwych użytkowników (CrUX: „Brak danych” dla wszystkich 5 adresów), bo ruch był za mały. Porównanie jest więc laboratoryjne i tak je opisuję.
Co rozważałem i odrzuciłem
Jak to działa: Worker tylko na /blog
Cała domena gsmx.pl przechodzi przez Cloudflare. Blog obsługuje Worker ze Static Assets, przypięty do dwóch tras: gsmx.pl/blog i gsmx.pl/blog/*. Wszystko inne trafia do serwera forum, jak przed migracją.
gsmx.pl/blogigsmx.pl/blog/*Worker bloga na Cloudflare
cała reszta domeny
forum na PHP, bez zmian
Plik _redirects
1466 reguł 301 ze starych adresów, wygenerowanych przez konwerter.
Gotowy plik z dist/
Adres
/blog/slug/to plikdist/blog/slug/index.html, bez uruchamiania kodu.Kod Workera, tylko gdy plik nie wystarczy
- stare adresy
?p= - 410 dla
wp-admin,xmlrpc.phpi usuniętych tagów - archiwa dat, RSS, stare miniatury
- stare adresy
Test kontraktu URL
2299 starych adresów, 0 niezgodnych
200 dla wpisu, 301 we właściwe miejsce, 410 dla usuniętego.
Trasa gsmx.pl/blog* wyglądałaby prościej, ale złapałaby też adresy forum, które zaczynają się od „blog”, na przykład /blogi/. Dlatego są dwie dokładne trasy, a test po przełączeniu sprawdzał również forum.
Astro buduje blog z base: '/blog' do katalogu dist/blog/, a Worker serwuje cały dist/. Adres /blog/slug/ odpowiada wtedy plikowi dist/blog/slug/index.html i Cloudflare oddaje go bez uruchamiania kodu. Kod Workera działa tylko tam, gdzie plik nie wystarczy:
- stare adresy z parametrami, na przykład
/blog/?p=1234, przekierowuje na właściwy wpis; - zwraca 410 Gone dla
wp-admin,wp-login.php,xmlrpc.php,wp-jsoni usuniętych tagów, żeby Google wiedział, że to celowe, a nie awaria; - obsługuje archiwa dat, RSS i stare ścieżki miniatur, których nie da się opisać statyczną regułą.
Reszta przekierowań, 1466 reguł, jest w pliku _redirects. Generuje go konwerter, nikt nie pisze go ręcznie. Ten plik musi leżeć w katalogu głównym dist/, a nie w dist/blog/, bo tylko tam Cloudflare go czyta. W dist/blog/ byłby zwykłym plikiem do pobrania.
Adresy bez ukośnika na końcu (/blog/slug) Cloudflare przekierowuje kodem 307, a nie 301 jak WordPress. Przyjąłem to świadomie: kanoniczne adresy mają ukośnik, a wersji bez niego prawie nie było w linkach ani w Search Console.
Konwerter: treść, media i to, co zostało po 20 latach
Konwerter czyta treść wpisów z REST API WordPressa (content.rendered), a resztę z lokalnej kopii bazy. Wyrenderowana treść ma już rozwinięte shortcody, galerie i akapity, czyli to, co widział czytelnik. Odtwarzanie tego z surowej treści w bazie byłoby dużym i kruchym kodem dla kilkuset wpisów. Z bazy biorę to, czego REST nie daje: meta tytuły i opisy z Rank Math, relacje i mapę mediów.
Stare wpisy zostały jako oczyszczony HTML w plikach Markdown, żeby wyglądały tak jak wcześniej. Nowe wpisy pisze się już w czystym Markdownie.
Co konwerter zrobił po drodze, według raportu migracji:
- 13 osadzeń YouTube z czasów Flasha zamienił na lekki odtwarzacz ładowany po kliknięciu. 4 inne osadzenia Flash zamienił na notkę z linkiem.
- Sprawdził każdy obrazek z obcego hosta. 21 jeszcze działało, więc pobrał je na blog. 152 obrazki w 53 wpisach prowadziły do serwisów, których już nie ma (tinypic, fotosik, imageshack i stare domeny). Usunął je z treści i wypisał w raporcie.
- Przygotował media: 508 plików, oryginały do 1600 px i warianty WebP z
srcset,widthiheight, żeby obrazki nie przesuwały tekstu. - Poprawił to, co było błędem: 3 slugi z typograficznymi myślnikami i cudzysłowami zakodowanymi w adresie, podkategorie dostępne pod dwoma adresami, kategorię
bada-2, 7 par zduplikowanych tagów (wi-fiiwifi,app-storeiappstore…). Każda zmiana dostała 301, a linki wewnętrzne w treści konwerter od razu przepisał na nowe adresy.
Działające, zaindeksowane adresy zostały, nawet jeśli nie były ładne. Zmiana adresu kosztuje, a ładniejszy slug nic nie daje.
Test kontraktu URL: 2299 adresów, 0 niezgodnych
Najtrudniejsza część migracji to nie szablony, tylko stare adresy. Dlatego z pełnej listy adresów WordPressa powstał skrypt, który dla każdego sprawdza, czy nowa strona odpowiada zgodnie z umową: 200 dla wpisu, 301 we właściwe miejsce dla zmienionego adresu, 410 dla usuniętego.
Uruchomiłem go na stagingu przed przełączeniem i na produkcji zaraz po nim:
| Źródło adresów | Adresy | Niezgodne |
|---|---|---|
| Wpisy z REST API | 336 | 0 |
| Tagi z REST API | 705 | 0 |
Stare adresy ?p= | 707 | 0 |
Media (ścieżki WP Hide i wp-content) | 300 | 0 |
| Miniatury | 194 | 0 |
| Autorzy, kategorie, strony | 33 | 0 |
Pozostałe: RSS, archiwa dat, sitemapy, ads.txt, weryfikacja GSC | 24 | 0 |
| Razem | 2299 | 0 |
To był warunek przełączenia: 100% zgodności albo nie ma wdrożenia.
Przełączenie bez ryzyka
Zanim przypiąłem Workera do produkcji, sprawdziłem strefę Cloudflare. Reguły przekierowań i Page Rules działają przed Workerem, więc jedna stara reguła na /blog przejęłaby ruch. Rocket Loader albo zaciemnianie adresów e-mail zmieniałyby HTML. Staging był w tej samej strefie, więc porównałem jego odpowiedź z plikiem z dist/. Różnił się tylko skryptem Web Analytics.
Samo przełączenie to wrangler deploy z dwiema trasami, 2 listopada 2026 o 9:02. WordPress nie został wyłączony. Został na hostingu, tylko ruch na /blog przestał do niego trafiać. Powrót to usunięcie tych dwóch tras i trwa kilka sekund.
- Krok 1
25.10: plan i pomiar „przed”
Spec migracji, Lighthouse, PageSpeed Insights i GTmetrix na 5 adresach, pełna lista adresów.
- Krok 2
25.10: konwerter i strona w Astro
Treść z REST API, media, mapa przekierowań, nowy układ bloga.
- Krok 3
25–27.10: Worker na stagingu
staging.gsmx.pl, test kontraktu URL, PageSpeed Insights stagingu.
- Krok 4
30.10: kategorie i opisy
Nowa struktura kategorii, osobne pola na nagłówek H1, tytuł SEO i opisy.
- Krok 5
2.11: przełączenie
Trasy Workera na produkcji, test 2299 adresów, sitemapy w Search Console.
- Krok 6
2.11: pomiar „po”
Ten sam skrypt, te same adresy, ta sama lokalizacja co 8 dni wcześniej.
- Krok 7
7.11: PageSpeed Insights i GTmetrix
Publiczne pomiary na produkcji, trzy poprawki reklam i 3 przebiegi PageSpeed Insights.
Wyniki: przed i po
Ta sama metoda co przed migracją: Lighthouse 12 lokalnie, 3 przebiegi, mediana. Strona po migracji ma reklamy AdSense, tak jak WordPress.
| Strona | Telefon | Komputer | LCP (telefon) | CLS (telefon) | Odpowiedź serwera | Żądania | Skrypty | Waga |
|---|---|---|---|---|---|---|---|---|
| Strona główna | 36 → 94 | 80 → 100 | 11,08 → 2,21 s | 0,335 → 0,039 | 1518 → 59 ms | 83 → 28 | 36 → 5 | 2433 → 709 KB |
| Kategoria | 61 → 93 | 91 → 100 | 5,79 → 2,20 s | 0,063 → 0,039 | 1407 → 48 ms | 87 → 28 | 38 → 5 | 1315 → 717 KB |
| Wpis z 2026 | 68 → 86 | 91 → 100 | 4,93 → 1,89 s | 0,075 → 0,042 | 1059 → 46 ms | 74 → 21 | 42 → 5 | 1150 → 505 KB |
| Wpis z 2010 | 60 → 89 | 89 → 100 | 6,66 → 2,21 s | 0,136 → 0,051 | 1222 → 46 ms | 78 → 20 | 42 → 5 | 1730 → 474 KB |
| Wpis z 2006 | 49 → 87 | 89 → 100 | 6,77 → 1,74 s | 0,155 → 0,045 | 1609 → 26 ms | 73 → 20 | 42 → 5 | 1581 → 474 KB |
Najbardziej zmieniła się odpowiedź serwera. WordPress składał każdą stronę w PHP przy każdym żądaniu. Teraz Cloudflare oddaje gotowy plik z najbliższego serwera. W tym pomiarze każda z 5 stron ma na komputerze 100.
PageSpeed Insights i GTmetrix
Lighthouse uruchamiany lokalnie łatwo podważyć, więc powtórzyłem też pomiary publiczne: PageSpeed Insights na tych samych 5 adresach, telefon i komputer, do tego GTmetrix z Seattle. Przed migracją zrobiłem w PageSpeed Insights po jednym przebiegu na adres. Po migracji zrobiłem trzy niezależne przebiegi i podaję medianę, bo jeden przebieg potrafi się różnić o kilkanaście punktów. Każdy wynik ma link do raportu.
| Strona | Telefon | Komputer | Waga (telefon) |
|---|---|---|---|
| Strona główna | 51 → 99 | 98 → 100 | 2761 → 536 KiB |
| Kategoria | 62 → 100 | 85 → 99 | 2005 → 519 KiB |
| Wpis z 2026 | 52 → 99 | 61 → 96 | 15 759 → 620 KiB |
| Wpis z 2010 | 85 → 100 | 92 → 100 | 14 359 → 618 KiB |
| Wpis z 2006 | 33 → 99 | 62 → 99 | 16 945 → 515 KiB |
Przed migracją: 25 października 2026, jeden przebieg, Lighthouse 13.5. Po migracji: 7 listopada 2026, mediana z 3 przebiegów, Lighthouse 13.6.
We wszystkich 30 przebiegach po migracji telefon ma 97–100, a komputer 95–100. Na WordPressie było to 33–85 i 61–98. Wpis z 2006 roku miał Total Blocking Time 7690 ms, teraz ma 40–160 ms. Wpisy ważą ok. 0,5–0,6 MiB zamiast 14–17 MiB. Ułatwienia dostępu i SEO mają 100, a na WordPressie miały 79–96 i 91–92.
Co wykrył pierwszy pomiar
Pierwszy przebieg PageSpeed Insights po migracji nie był taki równy. Strona główna na komputerze miała 90, a dwa stare wpisy CLS 0,084 i 0,113. Lokalny Lighthouse tego nie pokazywał. Za każdym razem winne było miejsce na reklamę w treści, którego AdSense nie wypełnił. Z Polski reklama zwykle się wyświetla, a PageSpeed Insights mierzy z USA i często jej nie dostaje.
Przyczyny były trzy, a znalazłem je po kolei:
- Puste miejsce na reklamę znikało od razu i tekst pod nim podskakiwał o ok. 300 px. Teraz znika tylko wtedy, gdy jest pod ekranem.
- AdSense najpierw daje reklamie 280 px, a gdy jej nie ma, wyjmuje ją z układu strony. Miejsce zarezerwowane na 250 px wracało do tej wysokości i akapit pod nim podjeżdżał o 30 px. Teraz rezerwa ma 280 px.
- Pusta reklama dostaje od AdSense szerokość 0. Wyśrodkowana przesuwała swoją zawartość o 330 px w prawo. Teraz jest wyrównana do lewej.
Po tych poprawkach CLS na komputerze wynosi najwyżej 0,011, a strona główna ma 100 we wszystkich trzech przebiegach. Wniosek ogólny: statyczna strona nie przesuwa się sama z siebie. Przesuwa ją to, co ładujesz na nią z zewnątrz, i to trzeba mierzyć z kilku miejsc, nie tylko z własnego komputera.
GTmetrix (Seattle, komputer, strona główna): ocena C → A, wydajność 62% → 90%, struktura 89% → 99%, LCP 3,3 s → 0,38 s. Raporty: przed i po. Ten pomiar GTmetrix powstał przed poprawkami reklam.

Co zostało: reklamy
Na telefonie wynik nie dochodzi do 100 przez reklamy, nie przez blog. Na stagingu PageSpeed Insights rozpisał czas procesora na wpisie z 2026 roku:
- skrypty reklam Google: 4,2 s;
- okno zgody na reklamy: 1,4 s;
- JavaScript samego bloga: 41 ms.
Gdy reklama zdąży się załadować w czasie pomiaru, wynik spada (ten wpis na stagingu: 69). Gdy nie zdąży, wynosi 99–100. Bez reklam nie da się tego poprawić, a reklamy zostają, bo zarabiają na blog.
Dwie rzeczy przy reklamach wyszły dopiero na produkcji:
- Pierwszy pomiar „po” dał CLS 0,155–0,215 na krótkich starych wpisach. Miejsce na reklamę w treści dostawało wysokość dopiero, gdy reklama się załadowała, i przesuwało akapit pod nią. Teraz każde miejsce rezerwuje 250 px od pierwszego renderu i CLS spadł do 0,04–0,05. Ta reszta pochodzi z okna zgody Google, nie z kodu bloga.
- Automatyczne reklamy AdSense wstawiały się, gdzie chciały. Wyłączyłem je dla całego
/blog/, a blog ma 4 własne jednostki w stałych miejscach. Forum ma automatyczne reklamy jak wcześniej. Reklamy wideo, które ważyły po kilkanaście MiB na wpis, zniknęły razem z WordPressem.
Czego nie wiem
Porównanie jest laboratoryjne. Na WordPressie Cloudflare Web Analytics zebrało przed przełączeniem tylko 4 pomiary z bloga, więc nie mam pomiaru od prawdziwych użytkowników „przed”. Nie mam też dowodu, że szybszy blog dostanie więcej ruchu z Google. Przy 16 kliknięciach w 16 miesięcy każda zmiana byłaby szumem. Okres obserwacji trwa do końca listopada, potem WordPress zostanie usunięty z hostingu.
Po migracji: wpisy z repozytorium
Statyczny blog nie oznacza bloga bez nowych wpisów. Wpis to plik Markdown w repozytorium. Na gsmx.pl nowe tematy zbiera automatyzacja z kanałów RSS. Ona też przygotowuje szkic ze źródłami i okładką i otwiera pull request. Przed scaleniem każdy szkic przechodzi sprawdzenie faktów ze źródłami, a potem blog buduje się i trafia na Cloudflare przez wrangler deploy. W pierwszym tygodniu po migracji tak ukazało się 15 wpisów. Wpis z datą w przyszłości czeka i pojawia się przy pierwszym buildzie po tej dacie.
Temat
Automatyzacja zbiera tematy z kanałów RSS albo autor pisze wpis sam.
Pull request
Wpis jako plik Markdown, ze źródłami i okładką.
Przegląd
- fakty sprawdzone ze źródłami
- tekst i data publikacji
Scalenie i wdrożenie
Build Astro i
wrangler deployna Cloudflare.Opublikowany
Wpis na gsmx.pl/blog
Bez panelu, bazy i PHP.
Co dalej: forum
Forum na gsmx.pl nadal działa na XenForo, czyli na PHP. Testuję dla niego kilka podejść w formie POC i MVP. Ostatnio przeniosłem je testowo na Flarum. Frontend Flarum opiera się jednak na jQuery i dość starym podejściu, więc najpewniej Flarum zostanie tylko zapleczem (headless), a stronę forum przejmie Astro z Vue. Więcej o tym napiszę wkrótce.
Kiedy taka migracja ma sens
Statyczne Astro pasuje, gdy treść zmienia się przy publikacji, a nie przy każdej wizycie. Tak jest na blogu, w serwisie newsowym i na stronie firmowej. Liczba autorów nie ma znaczenia. W WordPressie blog GSMX miał ich 17 i wszyscy zostali przeniesieni. Teraz autorem może być każdy, kto ma dostęp do repozytorium: pisze wpis jako plik Markdown w edytorze takim jak VS Code albo z pomocą asystenta AI i otwiera pull request. Na GSMX wszyscy autorzy są techniczni, więc brak panelu nikomu nie przeszkadza.
Gdy autorzy nie są techniczni i chcą zostać przy panelu, WordPress może zostać jako CMS, a Astro tylko wyświetla treść. Tak działa polo.blue od 2023 roku. Oba warianty opisuję na stronie o migracji z WordPressa do Astro.
Jeśli Twój WordPress jest wolny, a wtyczek przybywa przy każdej aktualizacji, zacznij od pomiaru i listy adresów, tak jak tutaj. To dwie rzeczy, od których zależy, czy migracja się uda. Szablony są łatwiejsze.




