Budowanie własnego bloga od zera w 2026 roku to świadomy wybór. Większość pisarzy po prostu wybiera gotową platformę hostingową i idzie dalej. Ja zdecydowałem się zbudować mój blog technologiczny na nowo przy użyciu Astro, ponieważ chciałem mieć kontrolę nad każdą warstwą stosu i nauczyć się wzorców, które faktycznie przyspieszają stronę statyczną, a nie tylko sprawiają, że jest inna. Efektem jest dwujęzyczna strona, która serwuje treści w języku japońskim i angielskim, domyślnie nie przesyła żadnego JavaScriptu i przenosi całą złożoność na mój komputer, zamiast obciążać przeglądarkę użytkownika.

Oto pięć wzorców, które sprawiły, że to zadziałało.

Content Collections z Zod

Content Collections w Astro robią więcej niż tylko organizowanie plików Markdown w foldery. Wymuszają one kontrakt między treścią a kodem. Dodałem schemat Zod do każdej kolekcji artykułów, co oznacza, że proces budowania (build) waliduje frontmatter, zanim jakakolwiek strona zostanie wyrenderowana.

Schemat wymaga pola language, które przyjmuje tylko dwie wartości: ja lub en. Nie ma żadnych niejasności co do tego, w jakim języku otrzyma treść czytelnik. Dodałem również pole pair, które łączy tłumaczenia ze sobą. Jeśli opublikuję post o Astro po japońsku, a później przetłumaczę go na angielski, oba pliki będą współdzielić to samo ID pary. Dzięki temu tworzenie przełącznika języka jest trywialne, ponieważ relacja jest jawna w danych, a nie wynika z nazw plików.

Daty pochodzą z frontmattera Markdown jako ciągi znaków, więc schemat automatycznie konwertuje je na rzeczywiste obiekty Date. Eliminuje to logikę manipulowania ciągami znaków wewnątrz szablonów stron. Najważniejszą korzyścią jest jednak sposób obsługi błędów. Jeśli plik nie posiada wymaganego pola lub używa nieprawidłowego kodu języka, proces budowania natychmiast się przerywa, wyświetlając jasny komunikat o błędzie. Naprawiam to w terminalu, zamiast odkrywać uszkodzony układ lub cichy błąd 404 już po wdrożeniu.

Potok konwersji artykułów

Nie pisałem każdego posta w tym nowym formacie od pierwszego dnia. Lata treści znajdowały się na platformach Zenn i Dev.to, a każda z nich ma swoje specyficzne cechy i własną składnię. Zamiast kopiować i wklejać treści, a następnie poprawiać je ręcznie, napisałem skrypt TypeScript, który konwertuje całe partie artykułów na standardowy Markdown.

Zenn używa własnej składni calloutów dla wskazówek i ostrzeżeń. Mój skrypt zamienia je na semantyczne znaczniki HTML aside, dzięki czemu renderują się spójnie w całej witrynie. Dev.to polega na tagach Liquid dla osadzonych elementów i specjalnych bloków. Potok tłumaczy je na zwykłe linki Markdown, które działają wszędzie.

Niektóre posty zawierają uwagi (asides) przeznaczone tylko dla oryginalnej platformy, takie jak zastrzeżenie o paywallach Medium lub ścieżkę do obrazu specyficzną dla Zenn. Zamykam je w komentarzach HTML, aby konwerter mógł je usunąć podczas migracji. Skrypt przetwarza pliki linia po linii, ale respektuje granice kodu. Gdy wykryje blok kodu (fenced code block), całkowicie pomija reguły transformacji. Uszkodzenie przykładu składni podważyłoby cały cel bloga technologicznego, dlatego parser działający linia po linii traktuje bloki kodu jako strefy nietknięte.

Uruchomienie jednej komendy pozwala teraz ponownie opublikować lata pisania bez łamania ani jednego linku czy calloutu.

Generowanie obrazów OGP w czasie budowania

Obrazy do udostępniania w mediach społecznościowych są zazwyczaj kwestią drugorzędną. Albo projektuje się je ręcznie, albo instaluje ciężką usługę działającą w czasie rzeczywistym, która generuje karty na żądanie. Nie chciałem ani jednego, ani drugiego. Każdy obraz Open Graph na tej stronie jest produkowany podczas budowania, dzięki czemu odwiedzający otrzymują jedynie lekki znacznik img wskazujący na statyczny plik PNG.

Używam Satori, które przyjmuje znaczniki JSX i renderuje je do SVG. Wynik jest wyraźny, przewidywalny i łatwy do szablonowania. Prawdziwa optymalizacja przyszła wraz z obsługą czcionek. Pełna japońska czcionka internetowa może łatwo przekroczyć pięć megabajtów. Ładowanie jej podczas budowania, a już na pewno proszenie przeglądarki o jej pobranie, byłoby absurdalne.

Zamiast tego stosuję subsetting Google Fonts. Skrypt analizuje tekst tytułu danego posta i żąda tylko dokładnego zestawu glifów potrzebnego do wyrenderowania tego ciągu znaków. Jeśli nagłówek zawiera czterdzieści unikalnych japońskich znaków, tylko te czterdzieści znaków jest przesyłanych przez sieć. Proces budowania pozostaje szybki, a wyrenderowany obraz nigdy nie pokazuje uszkodzonych bloków „tofu”, ponieważ podzbiór jest precyzyjny. Nic nie zostaje pozostawione przypadkowi w czasie działania aplikacji.

Tryb ciemny za pomocą tokenów Tailwind

Odmówiłem dekorowania każdego elementu klasami pomocniczymi (utility classes) dark:. Takie podejście słabo się skaluje i zaśmieca kod HTML szumem. Zdefiniowałem na nowo same tokeny kolorów, dzięki czemu ta sama nazwa klasy rozwiązuje się do różnych wartości w zależności od aktywnego motywu.

Używam właściwości CSS (custom properties) dla każdego tła i koloru tekstu. W trybie jasnym --color-white odpowiada wartości #ffffff. W trybie ciemnym ta sama nazwa zmiennej wskazuje na wartość bliską czerni. Mój kod HTML pozostaje całkowicie agnostyczny. Karta może używać bg-ui-surface i text-ui-primary, nie przejmując się porą dnia. Przełączanie motywu zmienia definicje zmiennych na poziomie root, a cały interfejs reaguje natychmiastowo.

Jedynym ryzykiem związanym z tym podejściem jest błysk jasnej treści przed załadowaniem arkuszy stylów. Rozwiązałem to za pomocą małego skryptu inline w sekcji head dokumentu. Uruchamia się on przed pierwszym renderowaniem (first paint), sprawdza localStorage oraz preferencje systemowe i natychmiast ustawia odpowiedni atrybut data. Ponieważ skrypt blokuje renderowanie tylko na kilka milisekund, użytkownik nigdy nie widzi rażącego białego błysku przed aktywacją trybu ciemnego.

Architektura wysp (Island Architecture) i Zero-JS

Głównym założeniem Astro jest to, że strona powinna zaczynać jako statyczny HTML. JavaScript pojawia się dopiero wtedy, gdy interakcja faktycznie tego wymaga. Potraktowałem to poważnie.

Unikałem Reacta w przypadku globalnego menu i przełącznika motywu. Oba elementy są obsługiwane przez niewielką ilość czystego JavaScriptu (vanilla JS) umieszczonego w pojedynczym module. Nie ma narzutu związanego z hydracją, porównywania wirtualnego DOM ani konieczności pobierania środowiska uruchomieniowego frameworka.

Jedyną ciężką biblioteką, której używam, jest Mermaid.js do renderowania diagramów z tekstu. Zamiast importować ją globalnie, zamknąłem ją w Intersection Observer. Obserwator śledzi kontenery diagramów. Gdy użytkownik przewinie stronę w odległość kilkuset pikseli od jednego z nich, skrypt dynamicznie wstrzykuje moduł Mermaid i renderuje diagram. Jeśli post nie zawiera diagramów, biblioteka ta nigdy nie łączy się z siecią. Początkowe ładowanie strony pozostaje lekkie, a przeglądarka „płaci” tylko za to, co czytelnik faktycznie widzi.

Podejście Build-First

Wspólnym mianownikiem każdego wzorca jest prosta zasada: jeśli możesz wykonać jakąś pracę podczas budowania (build), zrób to właśnie wtedy. Waliduj dane za pomocą Zod przed wdrożeniem strony. Konwertuj własną składnię platformy z wyprzedzeniem, a nie w momencie żądania. Renderuj obrazy społecznościowe do plików statycznych zamiast uruchamiać serwer. Rozwiązuj kolory motywów za pomocą tokenów, zamiast przesyłać logikę do każdego klienta. Odsuń w czasie ładowanie ciężkiego JavaScriptu, aż użytkownik faktycznie go będzie potrzebował.

Przesunięcie złożoności „w lewo”, czyli do etapu budowania, sprawia, że czas uruchomienia jest przewidywalny, rozmiar przesyłanych danych mały, a ciężar utrzymania możliwy do opanowania. Strona pozostaje szybka nie dzięki pojedynczemu trikowi, ale dlatego, że w przeglądarce użytkownika dzieje się po prostu mniej. To jest prawdziwa korzyść z wyboru architektury statycznej w 2026 roku.