Przejdź do treści

Z WordPressa na Astro + Cloudflare Workers – jak przebudowałem brylka.net

Przez lata ta strona stała na WordPressie z motywem Astra. Działała, ale płaciłem za to standardową cenę: PHP i baza na każde żądanie, wtyczki do wszystkiego, aktualizacje bezpieczeństwa i wynik w Lighthouse, którego nie chciało się pokazywać. Na początku czerwca 2026 przeniosłem brylka.net na statyczny stack Astro 5 + Cloudflare Workers – zero PHP, zero serwera, treść serwowana z edge’a CDN. W tym wpisie opisuję, jak to zrobiłem i jakimi zasadami się kierowałem.

Strona, którą właśnie czytasz, to efekt tej migracji. Jeśli ciekawi Cię, jak wygląda od środka – zajrzyj w źródło (Ctrl+U) albo do konsoli przeglądarki. Zostawiłem tam parę drobiazgów.

Naczelna zasada: jedyny dozwolony legacy

Przy przepisywaniu strony od zera łatwo ulec pokusie „poprawienia wszystkiego przy okazji”. Postawiłem sobie jedną twardą regułę:

Legacy to wyłącznie istniejąca treść i istniejące adresy URL. Reszta bez kompromisów.

To znaczy: każdy wpis, każda kategoria i każdy plik do pobrania musi zostać pod tym samym adresem co w WordPressie. Cała reszta – motyw, hosting, generowanie HTML, obrazy – idzie do kosza i powstaje na nowo. Dzięki temu nie traciłem pozycji w wyszukiwarce ani linków, które ktoś gdzieś zostawił, a jednocześnie nie wlokłem za sobą żadnego długu technicznego starego CMS-a.

Stack docelowy

Cały frontend to Astro 5 w trybie statycznym (SSG) – generuje czysty HTML w czasie builda, bez runtime’u po stronie klienta. Hosting to Cloudflare Workers w trybie static assets: pliki lądują na edge’u i są serwowane z najbliższej użytkownikowi lokalizacji.

WarstwaWordPress (było)Astro + Cloudflare (jest)
Generowanie stronPHP w czasie żądaniaAstro SSG (HTML w czasie builda)
Hostingwłasny baremetal: FreeBSD + nginx + php-fpmCloudflare Workers (edge)
Baza danychMySQL na każde żądaniebrak – wszystko statyczne
Obrazywtyczki, różne formatyWebP jako statyczne assety
Kolorowanie koduwtyczka + JavaScriptShiki (zero JS, w czasie builda)
DeployFTP / panelgit push → automatyczny build i wdrożenie

Kolorowanie składni robi Shiki – kompletnie bez JavaScriptu, bo podświetlanie liczone jest podczas builda. Obrazy trzymam jako statyczne pliki WebP w public/img/, a Cloudflare rozprowadza je po swoim CDN-ie. Żadnego R2, żadnego zewnętrznego serwisu obrazków.

Treść to baza, nie scraping

Najważniejsza decyzja: treści nie scrapowałem ze starej strony. Zamiast parsować wyrenderowany HTML (z całym śmieciem motywu), wziąłem zrzut bazy WordPressa jako źródło prawdy i napisałem skrypt, który odbudowuje z niego wpisy.

Pipeline wygląda tak:

dump SQL (mysqldump) ──► parser tabel ──► HTML Gutenberga
       └─► Markdown (cheerio + turndown + gfm)
       └─► obrazy z uploadów ──► WebP
       └─► tagi z taksonomii, opis z Yoast, język kodu z heurystyki

Z wp_posts wyciągam treść i metadane, z taksonomii – tagi i kategorie, z pól Yoast – opisy SEO. Bloki Gutenberga zamieniam na Markdown (cheerio do parsowania, turndown + wtyczka GFM do konwersji), a osadzone obrazy przepisuję na statyczne WebP. Efektem jest jeden plik .md na wpis – czytelny, wersjonowany w Gicie, niezależny od jakiegokolwiek CMS-a.

Dlaczego z bazy, a nie z HTML? Wyrenderowana strona to treść plus cały motyw, widżety i znaczniki wtyczek. Baza zawiera samą treść w czystej postaci. Odbudowa ze źródła daje czystszy Markdown i pełną kontrolę nad tym, co trafia do nowej strony.

Przy okazji znormalizowałem typografię – m.in. zamieniłem pauzy (em) na półpauzy (en), zgodnie z polską konwencją. Drobiazg, ale przy kilkudziesięciu wpisach robi różnicę.

URL-e 1:1 i przekierowania

To była najbardziej newralgiczna część. WordPress używał permalinku /%postname% – płaskie adresy bez ogona i bez .html. W Astro odtworzyłem to dokładnie:

// astro.config.mjs
export default defineConfig({
  build: { format: 'file' },   // /wpis.html zamiast /wpis/index.html
  trailingSlash: 'never',      // żadnych ukośników na końcu
});

Każdy wpis to plik src/content/posts/<slug>.md, a slug = dokładnie ta sama nazwa, co w starym adresie. Renderuje go jeden catch-all route [...slug].astro. URL /<slug> jest więc identyczny jak w WordPressie – to twardy kontrakt zachowania adresów.

Stare przekierowania też musiały przeżyć. WordPress miał wtyczkę Redirection z listą 301-ek; przeniosłem je do pliku public/_redirects (format Cloudflare), dorzucając przy okazji mapowania archiwów taksonomii i starego feeda:

# stare 301-ki z wtyczki Redirection
/stary-adres            /nowy-adres            301

# archiwa tagów WP → kategorie
/tag/*                  /category/:splat       301

# kanał RSS pod starym adresem
/feed                   /rss.xml               301

Dzięki temu nikt – ani użytkownik, ani robot wyszukiwarki – nie trafia na 404 po zmianie silnika.

Hosting na edge’u

Cloudflare Workers w trybie static assets potrzebuje minimalnej konfiguracji – wrangler.jsonc plus jeden mały worker.js. Apex brylka.net podpiąłem jako Custom Domain Workera, a www przekierowuję na apex regułą w panelu.

Deploy jest najprzyjemniejszą częścią całej zmiany. Nie ma FTP ani wgrywania paczek:

git push origin main

Push na main uruchamia Cloudflare Workers Builds: platforma sama odpala npm run build i wdraża wynik. Cała publikacja sprowadza się do commita. Co istotne, cutover był odwracalny – stary origin WordPressa mógł przez jakiś czas stać jako zapas, na wypadek gdyby coś wymagało powrotu.

Wydajność, SEO i GEO

Statyczny HTML serwowany z edge’a robi to, czego po nim oczekujesz – Lighthouse poszedł w górę na całej linii: Performance ~98, SEO 100, dostępność ~96–100, Best Practices ~100. Bez PHP i bez bazy nie ma czego optymalizować po stronie serwera; strona po prostu jest już gotowa.

Przy okazji zadbałem o sygnały dla wyszukiwarek i modeli językowych:

  • schema.orgPerson/ProfilePage na stronie głównej, BlogPosting przy wpisach, BreadcrumbList w okruszkach, WebApplication/MobileApplication przy projektach.
  • Open Graph – domyślny baner generowany z żywego logo, z poprawnymi wymiarami pod duże karty (WhatsApp, Facebook).
  • /llms.txt – maszynowo czytelny indeks treści pod modele językowe (GEO), generowany automatycznie z kolekcji wpisów i projektów.
  • GA4 z Consent Mode v2 – analityka odpala się dopiero po zgodzie z bannera cookie, tylko na produkcji.

Drobne smaczki

Skoro i tak pisałem szablon od zera, zostawiłem w nim dwa mrugnięcia do tych, którzy lubią zaglądać pod podszewkę: ramkę ASCII z logo (czcionka Braille’a) w komentarzu w źródle strony oraz stylowany komunikat w konsoli DevToolsów. Nic, co wpływa na działanie – po prostu wizytówka dla ciekawskich.

Efekt

Z grubego WordPressa z bazą i PHP zrobił się zestaw statycznych plików serwowanych z CDN-a, który publikuję jednym git push. Treść jest wersjonowana w Gicie, adresy się nie zmieniły, a wynik w Lighthouse przestał być powodem do wstydu. Najlepsze jest to, że utrzymanie sprowadza się do edycji plików Markdown – bez panelu, bez aktualizacji wtyczek, bez martwienia się o dziurę w kolejnym pluginie.

Jeśli chcesz zobaczyć, co na tym stacku stoi w praktyce, zajrzyj do portfolio – znajdziesz tam projekty webowe i mobilne, które rozwijam podobnym podejściem.