Przejdź do treści

Jak kontrolować JS w Astro? Dyrektywy klienta w praktyce

client:load, client:idle czy client:visible? Poznaj wpływ dyrektyw na wydajność Astro. Sprawdź praktyczne zasady i unikaj kosztownych błędów w produkcji.

Maciej Sala

Founder StriveLab

9 min czytaniaOpublikowano 24 kwietnia 2026 (Aktualizacja 7 lipca 2026)

W tym artykule omawiam pięć dyrektyw klienta: kiedy po nie sięgać, kiedy ich unikać i jakie błędy najczęściej wracają w audytach stron na Astro.

Po co są dyrektywy hydratacji client:* w Astro?

Astro domyślnie nie wysyła JavaScriptu do przeglądarki. Jeśli element z Reacta, Vue albo Svelte ma być interaktywny, musisz jawnie określić, kiedy pobrać jego kod i przeprowadzić . Brak automatycznego ładowania całego runtime'u to fundament architektury wysp i jej największa zaleta.

Jeżeli nie wiesz, czym są wyspy w Astro, zacznij od wpisu o Islands Architecture.

Dyrektywa to kontrakt wydajnościowy: wskazujesz frameworkowi, kiedy statyczny HTML ma dostać logikę po stronie klienta. Dzięki temu Astro może wyrenderować bazową treść bez hydratacji, a JavaScript wysłać tylko tam, gdzie jest naprawdę potrzebny.

Jest przy tym jedno ważne ograniczenie: propsy przekazywane do wyspy muszą być serializowalne. Astro zapisuje je w HTML, więc funkcje, instancje klas czy referencje do innych komponentów nie przejdą. Takie zależności trzeba przenieść do środka wyspy albo zastąpić danymi, które da się zapisać jako JSON.

client:load: natychmiastowa hydratacja komponentu

Code
---
import Navbar from '../components/Navbar.tsx';
---
 
<Navbar client:load />

client:load pobiera i uruchamia kod JavaScript komponentu jak najszybciej, kiedy tylko przeglądarka przetworzy HTML. Jest to najbardziej agresywna dyrektywa i powinna być zarezerwowana dla elementów, które muszą działać od razu.

Kiedy używać client:load:

  • Nawigacja główna z rozwijanym menu, które prawdopodobnie użytkownik otworzy, jako pierwsze.

  • Przycisk CTA w głównej sekcji (hero), który musi otworzyć okno modalne.

  • Komponent do zmiany motywu (ciemny/jasny), który ma zareagować na preferencje użytkownika bez migotania strony.

Kiedy NIE używać client:load:

  • Formularze kontaktowe w stopce, ponieważ do nich użytkownik musi przewinąć stronę i dlatego client:visible w zupełności wystarczy.

  • Komentarze pod wpisem (z analogicznego powodu, jak wyżej).

  • Galeria zdjęć, którą widać dopiero po kliknięciu w miniaturkę.

Kluczem do sukcesu jest selektywność. Każdy nieuzasadniony client:load to kilobajty blokujące i . Oznaczenie wszystkich wysp jako client:load niszczy całą przewagę wydajnościową Astro — i stawia Cię z powrotem na starcie Next.js, tyle że w innym ekosystemie.

client:idle: hydratacja, gdy przeglądarka jest bezczynna

Code
---
import NewsletterSignup from '../components/NewsletterSignup.tsx';
---
 
<NewsletterSignup client:idle />

client:idle czeka na wywołanie — moment, w którym przeglądarka zakończyła krytyczne zadania i procesor ma wolny cykl. Jeśli przeglądarka nie obsługuje requestIdleCallback, Astro używa zdarzenia load; w nowszych wersjach możesz też ustawić limit oczekiwania przez client:idle={{ timeout: 500 }}.

Kiedy używać:

  • Komponenty ważne, ale nie krytyczne dla pierwszej interakcji — zapis na newsletter, widżet czatu, baner zgody na pliki cookie. Przy zgodach marketingowych tryb ładowania komponentu musi być spójny z tym, kiedy uruchamiasz skrypty śledzące.

  • Widżety dostawców zewnętrznych opakowane w komponent frameworka, które wpływają na interaktywność, ale nie stanowią priorytetu.

  • Przyciski udostępniania w mediach społecznościowych.

Kiedy NIE używać:

  • Elementy znajdujące się poza początkowo widocznym obszarem strony, których załadowanie można odłożyć używając client:visible.

  • Krytyczne elementy interfejsu (okna modalne, główna nawigacja).

W praktyce client:idle często przesuwa hydratację poza krytyczny start strony — i właśnie takie odroczenie potrafi obniżyć koszt JavaScriptu widoczny w TBT oraz w diagnostyce Lighthouse.

client:visible: hydratacja po wejściu komponentu w viewport

Code
---
import CommentsSection from '../components/CommentsSection.tsx';
import ProductGallery from '../components/ProductGallery.tsx';
---
 
<article>
  <!-- długa treść artykułu -->
</article>
 
<CommentsSection client:visible />

client:visible wykorzystuje , aby pobrać i uruchomić JavaScript dopiero wtedy, gdy dana sekcja pojawi się w widocznym obszarze ekranu. Jeśli użytkownik do niej nie przewinie, paczka JS w ogóle nie zostanie pobrana.

client:visible to domyślna rekomendacja dla elementów poniżej linii przewijania. Daje realną oszczędność, bo kod ładuje się dopiero wtedy, gdy jest szansa na interakcję.

Kiedy używać client:visible:

  • Sekcja komentarzy pod artykułem.
  • Formularz kontaktowy w stopce lub na samym dole strony.
  • Galeria zdjęć znajdująca się poniżej opisu produktu.
  • Kalkulator lub interaktywny widżet osadzony w środku wpisu.

  • Karuzela z logotypami klientów.

Kiedy NIE używać client:visible:

  • Elementy pierwszego ekranu, bo są widoczne od razu. W takim miejscu client:visible nie optymalizuje ładowania, tylko dokłada narzut obserwatora (IntersectionObserver).

  • Wyspy współdzielące stan z innymi częściami interfejsu, bo opóźniona hydratacja może spowolnić synchronizację danych.

Możesz też skonfigurować parametr client:visible, aby rozpocząć pobieranie kodu JS zanim komponent fizycznie wejdzie w widoczny obszar (prefetching):

Code
<CommentsSection client:visible={{ rootMargin: "200px" }} />

Dzięki temu kod ładuje się, gdy komponent znajduje się np. 200 pikseli poniżej aktualnego widoku. Użytkownik nie odczuje żadnego opóźnienia, a w przypadku gdy w ogóle tam nie przewinie, oszczędzamy zasoby sieciowe.

client:media: hydratacja po spełnieniu media query

Code
---
import MobileNav from '../components/MobileNav.tsx';
import DesktopSidebar from '../components/DesktopSidebar.tsx';
---
 
<MobileNav client:media="(max-width: 768px)" />
<DesktopSidebar client:media="(min-width: 1024px)" />

client:media uruchamia hydratację dopiero po spełnieniu wskazanego media query. Przykładowo, na smartfonie DesktopSidebar nie pobierze swojego kodu JS, a MobileNav zostanie pominięty na komputerze stacjonarnym. Warunek jest nasłuchiwany na żywo: po obrocie tabletu albo zwężeniu okna hydratacja ruszy w momencie dopasowania, ale już zhydratowany komponent pozostaje interaktywny nawet po późniejszej zmianie rozmiaru.

Kiedy używać client:media:

  • Elementy interfejsu przeznaczone wyłącznie na urządzenia mobilne, czyli menu typu hamburger, wysuwane panele dolne (bottom sheets), galerie obsługujące gesty przesunięcia (swipe).

  • Elementy wyłącznie desktopowe, takie jak rozbudowane menu rozwijane po najechaniu kursorem, skomplikowane filtry wykorzystujące mechanizm przeciągnij i upuść.

  • Responsywne widżety, które mają kompletnie inną logikę działania w zależności od rozmiaru ekranu.

Kiedy NIE używać client:media:

  • Kiedy komponent dostosowuje się tylko wizualnie (za pomocą CSS media queries), ale nie zmienia logiki biznesowej.

  • Gdy zachowanie różni się zaledwie szczegółem, wtedy prościej jest użyć client:visible i wewnątrz komponentu weryfikować warunki przez window.matchMedia.

Po tę dyrektywę sięgam rzadko, ale w specyficznych sytuacjach, np. przy ciężkim wysuwanym panelu mobilnym, który na desktopie nie istnieje, pozwala zaoszczędzić dziesiątki kilobajtów transferu. Innymi słowy, wdrażamy ją tylko wtedy, gdy logika po obu stronach ekranu jest wyraźnie inna.

client:only: komponent wyłącznie po stronie klienta

Code
---
import ChartWidget from '../components/ChartWidget.tsx';
import ClientSideMap from '../components/InteractiveMap.tsx';
---
 
<ChartWidget client:only="react" />
<ClientSideMap client:only="react" />

client:only to sygnał dla Astro, żeby nie renderować komponentu po stronie serwera () i wygenerować go wyłącznie w przeglądarce. Działa to odwrotnie do pozostałych dyrektyw.

Kiedy używać client:only:

  • Komponenty zależne w fazie renderowania od obiektów globalnych przeglądarki takich jak window, document czy localStorage, ponieważ bez tego proces SSR zakończyłby się błędem.

  • Biblioteki, które nie posiadają wsparcia dla SSR (np. niektóre nakładki na Three.js, react-leaflet, edytory tekstu typu WYSIWYG).

  • Komponenty bazujące na danych z bazy IndexedDB lub service workerów.

  • Elementy, których wygląd jest bardzo mocno uzależniony od dokładnego rozmiaru okna, a wersja wyrenderowana na serwerze i tak wymagałaby natychmiastowego przebudowania przez JS.

Kiedy NIE używać client:only:

  • We wszystkich innych przypadkach. Użycie client:only dezaktywuje mechanizm SSR, przez co rezygnujesz z błyskawicznego serwowania gotowej treści. Zamiast tego zmuszasz użytkownika do oczekiwania, aż pobierze się JS i wygeneruje komponent bezpośrednio w jego przeglądarce.

Sprawdzoną praktyką przy korzystaniu z client:only jest dodanie zastępczego szkieletu (placeholdera):

Code
<div class="min-h-[400px] flex items-center justify-center">
  <ClientSideMap client:only="react">
    <p slot="fallback">Ładowanie mapy...</p>
  </ClientSideMap>
</div>

Dzięki temu zabiegowi użytkownik na czas wczytywania widzi zarezerwowaną przestrzeń, układ strony nie skacze, a wskaźnik przesunięcia układu () pozostaje nienaruszony, co jest ważne dla zielonych wyników .

Porównanie dyrektyw client:* w Astro

DyrektywaKiedy ładuje JSKiedy używaćKiedy unikać
client:loadNatychmiastKrytyczne interakcje na pierwszym ekranie (nawigacja, główne przyciski)Elementy widoczne dopiero po przewinięciu
client:idleGdy przeglądarka jest bezczynnaWażne, ale nie priorytetowe elementy (newsletter, czat)Krytyczny interfejs, komponenty reagujące na przewijanie
client:visibleGdy element wchodzi w pole widzeniaWszystko poniżej pierwszego ekranuElementy widoczne od razu po załadowaniu
client:mediaGdy spełnione są reguły media queryInterfejsy specyficzne tylko dla mobile / desktopGdy zmienia się wyłącznie wygląd, a nie logika
client:onlyNatychmiast, ale bez SSR — HTML powstaje dopiero w przeglądarceBiblioteki niekompatybilne z SSR, wymagające okna przeglądarkiKażdy element, który można bez problemu wyrenderować na serwerze

Budżet hydratacji w Astro

W większych projektach warto ustalić limit kodu klienta dla każdego typu wyspy, ponieważ określenie takiego budżetu chroni przed sytuacją, w której strona Astro ma być lekka, a w praktyce wysyła kilka masywnych paczek.

maksymalna liczba wysp z client:load na pierwszym ekranie
1–2maksymalna liczba wysp z client:load na pierwszym ekranie
JavaScript dla elementów czysto wizualnych bez interakcji
0 KBJavaScript dla elementów czysto wizualnych bez interakcji
  • Pierwszy ekran: maksymalnie 1–2 interaktywne wyspy z client:load.
  • Niższe sekcje strony: client:visible jako domyślny wybór.
  • Detale wizualne i dekoracje: brak hydratacji, chyba że wprost wpływają na konwersję.
  • Komponenty wymagające API przeglądarki: client:only — brak SSR jest w tym wypadku nieunikniony.
  • Zależności współdzielone: regularna weryfikacja, czy mała wyspa nie importuje całej biblioteki wykresów, edytora WYSIWYG albo kitu.

Najczęstsze błędy przy dyrektywach client:*

W audytach stron Astro notorycznie spotykam trzy te same kategorie błędów:

1. Nadużywanie client:load. Przykładem jest serwis na Astro, który ma osiem wysp oznaczonych client:load i ładuje cały ich JavaScript już na starcie. W takiej konfiguracji architektura wysp istnieje głównie na papierze.

2. Layout owinięty w jeden wielki React z client:load. Programista przyzwyczajony do Next.js może odruchowo budować Layout.tsx z nagłówkiem, treścią i stopką, a potem oznaczyć całość client:load. Fundament strony musi być .astro, natomiast komponenty frameworkowe osadzamy punktowo i wyłącznie tam, gdzie konieczna jest interakcja.

3. client:only bez elementu zastępczego. Komponent ładuje się po 1,5 s, strona nieprzyjemnie skacze, a CLS niebezpiecznie rośnie. Zawsze ustaw kontener zastępczy z odpowiednią wysokością na czas ładowania.

Praktyczny przykład: karta produktu e-commerce w Astro

Przeanalizujmy dobór odpowiednich dyrektyw przy projektowaniu standardowej podstrony produktowej:

Code
---
// src/pages/produkt/[slug].astro
import Layout from '../../layouts/Layout.astro';
import Header from '../../components/Header.astro';
import ProductImages from '../../components/ProductImages.tsx';
import AddToCart from '../../components/AddToCart.tsx';
import ProductDescription from '../../components/ProductDescription.astro';
import Reviews from '../../components/Reviews.tsx';
import RecentlyViewed from '../../components/RecentlyViewed.tsx';
import Footer from '../../components/Footer.astro';
 
const { product } = Astro.props;
---
 
<Layout>
  <Header />
 
  <main>
    <!-- Galeria widoczna od razu, istnieje wysokie prawdopodobieństwo wejścia w interakcję -->
    <ProductImages images={product.images} client:load />
 
    <!-- Przycisk dodawania do koszyka (CTA) o kluczowym znaczeniu dla konwersji -->
    <AddToCart productId={product.id} client:load />
 
    <!-- Czysto statyczny tekst opisu, nie generuje JS -->
    <ProductDescription content={product.description} />
 
    <!-- Sekcja opinii umieszczona niżej, aktywuje się dopiero przy przewijaniu -->
    <Reviews productId={product.id} client:visible />
 
    <!-- Karuzela "Ostatnio przeglądane" pobiera kod JS w wolnej chwili -->
    <RecentlyViewed client:idle />
  </main>
 
  <Footer />
</Layout>

Taka konfiguracja od razu pokazuje pełną strukturę strony, bez oczekiwania na pobranie skryptów. Krytyczne strefy interfejsu (galeria zdjęć, przycisk zakupu) są szybko hydratowane i gotowe do akcji, a skrypty opinii ładują się dopiero wtedy, gdy użytkownik przewinie stronę do danej sekcji. Elementy o niższym priorytecie, takie jak karuzela sugerująca inne produkty, nie opóźniają pierwszego renderowania zawartości na ekranie.

Audyt hydratacji własnej strony Astro

Jeśli rozwijasz już projekt w Astro, wdróż ten czteropunktowy audyt:

  1. Przeszukaj kod pod kątem client:load — każda taka deklaracja musi być nieodzowna na pierwszym ekranie.
  2. Zmień na client:visible każdą dyrektywę dla elementów poza pierwszym ekranem.
  3. Znajdź każde client:only — udokumentuj komentarzem, dlaczego SSR jest tu niemożliwy. Część przypadków da się przepisać.
  4. Uruchom Lighthouse Mobile. Pilnuj zwłaszcza TBT, INP, CLS i LCP; TBT poniżej 200 ms to dobry punkt odniesienia dla strony z lekką interaktywnością.

Jeśli chcesz zlecić profesjonalny audyt techniczny SEO Twojej witryny w Astro — wejdź na wyższy poziom i odezwij się.

Ultraszybkie projekty, łączące lekkość ze skalowalnością.
Astro

Często zadawane pytania

Czym różni się `client:idle` od `client:visible`?

client:idle ładuje JS po początkowym ładowaniu strony, gdy przeglądarka ma wolny cykl lub po wskazanym limicie czasu — niezależnie od pozycji komponentu na stronie. client:visible ładuje JS dopiero, gdy komponent wchodzi w widoczny obszar ekranu: jeśli użytkownik nie przewinie do niego, JS nie załaduje się w ogóle. client:visible to zazwyczaj lepszy wybór dla elementów poniżej linii przewijania.

Czy mogę łączyć dyrektywy na jednym komponencie?

Nie. Każdy komponent przyjmuje dokładnie jedną dyrektywę hydratacji. Przy złożonych scenariuszach, np. gdy na mobile chcesz ładować JS przez client:idle, a na desktopie przez client:visible, rozwiązanie musi leżeć wewnątrz komponentu albo przez duplikację z client:media.

Czy `client:only` ma wpływ na SEO?

Tak i zwykle jest to wpływ negatywny. client:only wyłącza SSR, więc zawartość komponentu nie trafia do pierwszego HTML. Roboty indeksujące potrafią renderować JavaScript, ale zwiększa to koszt renderowania i ryzyko opóźnionej indeksacji. Dla najważniejszej treści pod kątem SEO client:only jest błędem architektonicznym.

Dlaczego mój komponent z `client:load` ładuje się powoli?

Przyczyną jest rozmiar paczki kodu, a nie sama dyrektywa. client:load oznacza „tak szybko, jak to możliwe", ale przeglądarka musi pobrać JS z sieci. Przy 200 KB zależności client:load nie pomoże, ponieważ optymalizacja musi trafić w sam komponent, czyli w dynamiczne importy, tree shaking czy lżejsze zależności.

Czy muszę używać dyrektyw dla komponentów `.astro`?

Nie. Komponenty .astro renderują się statycznie w build time lub przez SSR i nie wysyłają JavaScriptu do klienta. Dyrektywy client:* dotyczą wyłącznie komponentów z frameworków UI: React, Vue, Svelte, Solid, Preact.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Architektura wysp w Astro — czym są wyspy i dlaczego zero JS domyślnie zmienia zasady gry

Zero JavaScript domyślnie brzmi jak jakaś reklama, ale dane jasno pokazują, że to architektoniczna decyzja, która daje do myślenia: Lighthouse powyżej 95, TBT bliski zera, Google dostaje czysty HTML od razu. Klasyczne aplikacje SPA wysyłają do przeglądarki cały framework, nawet jeśli większość strony to statyczna treść. W tym artykule rozkładam mechanizm wysp na części i pokazuję, kiedy daje realne korzyści, a kiedy nie.

Maciej Sala

Maciej Sala

Founder StriveLab

Astro.js vs Next.js w 2026 — kompleksowe porównanie frameworków

Artykuł nie jest manifestem o porzuceniu jednej technologii na rzecz drugiej, lecz bardziej analizą, w której staram się pokazać obiektywnie, w których obszarach Astro dostarcza bezdyskusyjną przewagę wydajnościową, a w jakich scenariuszach Next.js pozostaje niezastąpionym wyborem.

Maciej Sala

Maciej Sala

Founder StriveLab

Wieloframeworkowa architektura wysp w Astro: React, Vue i Svelte w jednym projekcie

Zero JavaScript by default to chwytliwy slogan. Architektura, którą on opisuje, to coś większego: cały projekt może być statycznym HTML, a interaktywność — precyzyjnie rozmieszczonymi wyspami. Każda wyspa to niezależna jednostka z własnym frameworkiem, własnym cyklem hydratacji i własnym kosztem runtime. React, Vue, Svelte, Solid — na jednej stronie, pod jedną konfiguracją Astro. To nie eksperyment — to strategia migracji i narzędzie dla organizacji, które nie mogą sobie pozwolić na przepisanie wszystkiego od zera.

Maciej Sala

Maciej Sala

Founder StriveLab