Mamy stronę usługową w Astro: hero z obrazem, sekcje oferty, dowód społeczny, formularz kontaktowy jako wyspa React, analityka i czat. Świeży build dał 94/100 na mobile — wysoko, bo Astro nie wysyła zbędnego JavaScriptu. Ale klient chciał komplet. Lighthouse to narzędzie Google, które uruchamia techniczny test strony i wskazuje potencjalne problemy z wydajnością, SEO i dostępnością. wskazał trzy obszary do domknięcia: obrazy, fonty i skrypty third-party.
- Lighthouse Mobile po optymalizacji
- 94 → 100
- LCP po uporządkowaniu obrazu hero
- 2.1 s → 0.9 s
- CLS po wdrożeniu Fonts API
- 0.09 → 0.00
Punkt wyjścia: dlaczego Astro startuje wysoko
Zanim zaczniemy optymalizować, warto zrozumieć, skąd te 94 punkty na starcie. Astro renderuje stronę jako statyczny HTML i domyślnie nie wysyła do przeglądarki frameworkowego JavaScriptu. Sekcje takie jak hero, oferta czy stopka to komponenty .astro — czysty HTML, zero kosztu po stronie klienta.
To strukturalna przewaga nad aplikacyjnym modelem React/Next, gdzie nawet statyczna strona ładuje runtime frameworka. Szerzej rozkładam to w artykule Astro.js vs Next.js. Dla Lighthouse oznacza to niski TBT (Total Blocking Time) to laboratoryjna metryka sumująca czas, w którym główny wątek był zablokowany na tyle długo, że nie mógł zareagować na interakcję. Służy jako przybliżenie INP w środowisku bez realnych użytkowników. i dobry punkt wyjścia pod INP (Interaction to Next Paint) to Core Web Vital mierzący responsywność strony. Zastąpił FID i ocenia, ile czasu mija od interakcji użytkownika do najbliższego odrysowania ekranu, biorąc pod uwagę wszystkie interakcje w trakcie sesji. bez żadnej pracy.
Ostatnie punkty to nie walka z frameworkiem — to dopracowanie zasobów, które framework przepuszcza bez zmian: obrazów, fontów i skryptów zewnętrznych.
Krok 1: Obrazy — astro:assets zamiast surowego <img>
Największy problem był w hero. Obraz wstawiony jako surowy <img> ładował się w pełnej wadze, bez WebP/AVIF i bez wymiarów — stąd duży LCP, czyli Largest Contentful Paint, mierzy czas do wyrenderowania największego widocznego elementu — oznaczenie go preload przyspiesza jego załadowanie. i przesunięcia układu.
Rozwiązaniem jest komponent <Image> z astro:assets to wbudowany moduł Astro do optymalizacji obrazów — generuje WebP/AVIF, responsywny srcset i wymusza wymiary, by uniknąć CLS.:
Trzy rzeczy zadziałały naraz. WebP/AVIF generuje się automatycznie podczas builda — bez ręcznej obróbki ani pipeline'u graficznego. Wymiary width i height sprawiają, że przeglądarka rezerwuje miejsce jeszcze przed załadowaniem obrazu, więc układ nie skacze. loading="eager" z fetchpriority="high" wyciąga obraz hero poza kolejkę lazy loading i ładuje go jako pierwszy — bo to on jest elementem LCP, który Lighthouse mierzy. W efekcie LCP spadł z 2.1 s do 0.9 s, a pozostałe obrazy dostały loading="lazy" automatycznie.
Krok 2: Fonty — Fonts API zamiast Google Fonts z CDN
Drugim źródłem CLS (Cumulative Layout Shift) to Core Web Vital mierzący nieoczekiwane przesunięcia elementów podczas ładowania i działania strony. Animacje psują go wtedy, gdy ruszają właściwości layoutowe albo gdy pojawiający się element nie ma zarezerwowanego miejsca. były Google Fonts ładowane z zewnętrznego CDN: zewnętrzny request, FOUT i skok układu, gdy właściwy krój się doczytał.
Rozwiązaniem jest Fonts API ustabilizowane w Astro 6 (top-level klucz fonts), które self-hostuje krój i generuje dopasowane fallbacki:
Astro podczas builda pobiera krój, kopiuje go do lokalnych zasobów, generuje font-display: swap z fallbackiem o zbliżonych metrykach i dodaje preload. Produkcja nie odpytuje serwerów Google, a tekst nie skacze przy podmianie kroju. CLS z fontów spadł z 0.09 do 0.00. Dla polskich znaków kluczowy jest latin-ext w subsets.
Krok 3: Skrypty third-party — leniwe ładowanie
Analityka i widget czatu ładowały się synchronicznie, blokując główny wątek i podbijając TBT. W Astro skrypty kontrolujesz dyrektywami i atrybutami ładowania:
Dla cięższych integracji (czat, embedy) najlepszą strategią jest załadowanie ich dopiero po interakcji albo gdy wejdą w viewport — przez wyspę z client:visible albo client:idle, zamiast wpinać skrypt w <head>. Wszystko, co nie jest krytyczne dla pierwszego renderu, schodzi poza ścieżkę krytyczną.
W wyniku tych działań TBT spadł poniżej progu, a INP ustabilizował się na bardzo niskim poziomie.
Krok 4: Dyrektywy client — selektywna hydratacja
To najważniejsza dźwignia specyficzna dla Astro. Formularz kontaktowy był wyspą React z client:load — hydratował się natychmiast, mimo że użytkownik dociera do niego dopiero po przewinięciu strony.
Zmiana na client:visible:
Każdy client:load to JavaScript blokujący wątek od razu po załadowaniu. client:visible odkłada koszt do momentu, gdy komponent jest realnie potrzebny — a jeśli użytkownik nie doscrolluje, kod nie pobiera się wcale. To główny mechanizm, którym w Astro sterujesz INP i TBT. Szerzej rozkładam dyrektywy w osobnym artykule.
Reguła: client:load tylko dla interfejsu krytycznego na pierwszym ekranie. Wszystko poniżej — client:visible.
Wynik końcowy
| Metryka | Przed | Po |
|---|---|---|
| Lighthouse Mobile | 94 | 100 |
| Lighthouse Desktop | 98 | 100 |
| LCP | 2.1 s | 0.9 s |
| INP | 90 ms | 40 ms |
| CLS | 0.09 | 0.00 |
| TBT | 180 ms | 30 ms |
Checklist optymalizacji Astro
Pięć punktów, które w tym case study dały największy efekt i które warto wbudować w każdy projekt Astro od początku:
- Obrazy przez
astro:assets—<Image>zwidth/height,loading="eager"ifetchpriority="high"na elemencie LCP. - Fonty przez Fonts API (Astro 6) z
latin-extdla polskiego — self-hosting zamiast Google Fonts z CDN eliminuje FOUT i layout shift. client:visiblejako domyślna dyrektywa;client:loadtylko dla interfejsu krytycznego na pierwszym ekranie.- Skrypty third-party
async/deferalbo jako wyspaclient:idle/client:visible— nigdy synchronicznie w<head>. - Testuj na produkcyjnym URL przez PageSpeed Insights, nie tylko lokalnie — Lighthouse w dev nie mierzy TTFB ani opóźnień CDN.
Astro vs Next.js — ta sama meta, inny start
Ten case study jest świadomym bliźniakiem mojego case study 100/100 w Next.js. Różnica nie leży w technikach — obrazy, fonty, lazy loading skryptów obowiązują tak samo. Leży w punkcie startu: Next.js zaczyna niżej (runtime React do okiełznania), Astro wyżej (zero JS domyślnie), ale obie strony dochodzą do 100/100 tą samą dyscypliną.

