Na czym polega streaming w Next.js?
W klasycznym renderowaniu serwerowym bez mechanizmu streamowania aplikacja musi zebrać wszystkie dane potrzebne do zbudowania całej strony, zanim użytkownik cokolwiek zobaczy. Mimo że same zapytania mogą biec równolegle, pojedyncze wolne oczekiwanie umieszczone wysoko w drzewie komponentów skutecznie blokuje wyrenderowanie wszystkiego poniżej.
Streaming całkowicie zmienia ten model, pozwalając wysłać wstępną powłokę strony wraz z fallbackami, zanim zakończą się wolniejsze operacje, a następnie sukcesywnie uzupełniać treść w miarę gotowości poszczególnych granic. Warto jednak pamiętać, że natychmiastowe ładowanie nie jest dane raz na zawsze, ponieważ czas dotarcia pierwszego fragmentu zależy między innymi od zadań wykonywanych przed granicą, generowania metadanych dla botów oraz pośredniego buforowania po drodze.
Next.js App Router obsługuje streaming natywnie, wykorzystując React Suspense i architekturę Server Components.
Jak działa Suspense w Server Components?
<Suspense> to granica, która mówi Reactowi: „wyświetl fallback (np. skeleton), a gdy dane będą gotowe, wtedy podmień na właściwą treść".
Kolejność wyświetlania przy Suspense
Niezależne granice Suspense mogą ujawniać się zgodnie z gotowością danych, a nie wyłącznie według kolejności w JSX.
Jeśli RevenueChart dostanie dane w 500 ms, a DashboardStats w 2 s, chart pojawi się pierwszy, mimo że w kodzie jest niżej. Każdy <Suspense> boundary to niezależna „wyspa" streamowania.
To nie znaczy, że struktura JSX jest obojętna. Zagnieżdżona granica nie ujawni się przed gotowością granicy nadrzędnej, a fetch uruchomiony dopiero po zakończeniu innego komponentu stworzy waterfall. Granice kontrolują sposób ujawniania UI; moment rozpoczęcia zapytań kontrolujesz strukturą komponentów i miejscem utworzenia obietnic.
loading.tsx: Suspense na poziomie routingu
Next.js App Router ma wbudowany mechanizm, który automatycznie owija page.tsx w <Suspense>. Wystarczy dodać plik loading.tsx w tym samym katalogu.
Różnica jest prosta: loading.tsx tworzy granicę Suspense dla page.tsx i potomnych segmentów danego poziomu routingu, podczas gdy współdzielony layout pozostaje widoczny i interaktywny. Ręczne <Suspense> pozwala wydzielić konkretne sekcje niezależnie. W praktyce możesz łączyć oba podejścia.
Projektowanie dobrych skeletonów w Next.js
to narzędzie UX, które redukuje postrzegany czas ładowania, a dobrze zaprojektowany skeleton:
Odwzorowuje kształt docelowej treści, czyli użytkownik mentalnie widzi stronę zanim się załaduje.
Unika migotania: dla bardzo szybkich danych rozważ opóźnione pokazanie fallbacku; próg dobierz na podstawie pomiarów, nie sztywnej reguły 200 ms.
Jest lekki i nie zawiera ciężkich animacji, które obciążają GPU.
Fallback powinien być w pełni dostępny dla każdego użytkownika, dlatego dekoracyjne kształty należy ukryć przed czytnikami za pomocą atrybutu aria-hidden, a stan ładowania komunikować na kontenerze sekcji przy użyciu aria-busy="true" lub dedykowanego elementu z role="status". Warto również wyłączyć animacje dla użytkowników preferujących ograniczony ruch, używając zapytania prefers-reduced-motion. Po pomyślnym załadowaniu właściwej treści należy niezwłocznie usunąć atrybut aria-busy, pamiętając, aby nie ogłaszać każdej karty osobno, co mogłoby przytłoczyć odbiorcę korzystającego z technologii asystujących.
Unikanie flash of loading state
Jeśli dane ładują się szybko, skeleton migocze na ułamek sekundy i to jest gorsze niż brak skeletonu. W tej sytuacji CSS może pomóc:
Skeleton dopasowany do rodzaju sekcji
Nie buduj jednego uniwersalnego placeholdera dla całej aplikacji. Karta produktu potrzebuje miejsca na zdjęcie, cenę i CTA, tabela powinna zachować liczbę kolumn, a artykuł może pokazać jedynie szerokości akapitów. Dzięki temu po doładowaniu danych układ nie przeskakuje, a użytkownik od razu rozumie strukturę widoku.
W ramach prostego testu porównaj wymiary skeletonu z finalnym komponentem i sprawdź CLS w Lighthouse albo danych rzeczywistych. Jeżeli fallback zmienia wysokość sekcji lub spycha najważniejszą treść, ładowanie wizualnie szkodzi nawet wtedy, gdy streaming działa technicznie poprawnie.
Streaming i Suspense a Core Web Vitals
Streaming może wpływać na metryki , których dane rzeczywiste Google wykorzystuje jako jeden z sygnałów jakości strony:
LCP przy streamingu w Next.js
Streaming może poprawić LCP tylko wtedy, gdy kandydat LCP znajduje się w szybko dostarczonej powłoce albo rozwiązuje się wcześniej niż bez streamingu. Jeśli największym elementem jest obraz lub hero ukryte za wolną granicą, LCP nadal będzie późny, a zastąpienie dużego skeletonu właściwą treścią może wyznaczyć nowego kandydata LCP. Mierz wynik w danych rzeczywistych, a nie na podstawie samego użycia Suspense.
INP przy streamingu i Suspense
React łączy streaming z selektywną hydratacją, więc może wcześniej priorytetyzować interaktywne fragmenty. Nie gwarantuje to poprawy INP: ciężki kod klienta, długie zadania na głównym wątku i kosztowne handlery nadal decydują o opóźnieniu interakcji. Server Components bez JavaScriptu klienta nie wymagają hydratacji; hydratowane są interaktywne Client Components.
CLS i stabilność skeletonów
Streaming może pogorszyć CLS, jeśli skeletony mają inne wymiary niż docelowa treść. Rozwiązanie: projektuj skeletony z identyczną wysokością i układem jak finalne komponenty.
Streaming a SEO: co widzi Googlebot?
Streaming w App Routerze nadal jest renderowaniem serwerowym, ale nie warto opierać SEO na założeniu, że każdy robot poczeka na każdą spóźnioną granicę. Treść kluczową dla znaczenia strony trzymaj w statycznej powłoce albo szybko rozwiązującej się granicy. Cięższe sekcje pomocnicze streamuj później.
Najbezpieczniejsze założenie jest takie:
- Tytuł, lead, H1, opis i główna treść powinny być dostępne bez długiego czekania na request-time data.
- Skeleton nie jest treścią SEO i traktuj go wyłącznie jako element UX.
- Jeśli ważna sekcja streamuje się długo albo zależy od personalizacji, nie zakładaj, że każdy crawler zobaczy ją tak samo szybko jak użytkownik.
- Weryfikuj efekt w praktyce przez Google Search Console i renderowany HTML.
W Next.js 16 dynamiczne generateMetadata może być streamowane niezależnie od UI i wstrzykiwane po rozwiązaniu obietnicy. Dla botów wymagających metadanych w <head>, takich jak Twitterbot, Slackbot czy Bingbot, Next.js domyślnie wyłącza ten mechanizm i czeka na metadane przed rozpoczęciem streamingu. Zachowanie można zmienić opcją htmlLimitedBots, ale nie rób tego bez testów crawlerów i podglądów udostępniania.
Weryfikacja: użyj inspekcji adresu URL w Google Search Console, wykonaj test wersji opublikowanej i sprawdź wyrenderowany HTML. Dodatkowo porównaj „Wyświetl źródło strony” z DOM-em w DevTools, bo źródło pokazuje powłokę wysłaną przez serwer, a DOM także fragmenty dołączone później.
Zaawansowane wzorce Suspense w Next.js
Zagnieżdżony Suspense
Możesz zagnieżdżać granice, tworząc hierarchię ujawniania treści:
Grupowanie granic Suspense
Jeśli kilka komponentów powinno pojawić się razem, grupuj je w jednej granicy. Jeżeli mają niezależne czasy oczekiwania i mogą pojawiać się osobno, oddzielne granice dadzą szybsze częściowe ujawnienie.
Streaming z Promise.all dla równoległych fetchów
Promise.all uruchamia niezależne operacje równolegle, ale pokazuje cały komponent dopiero po zakończeniu najwolniejszej i odrzuca całość, gdy jedna obietnica zawiedzie. Użyj go, gdy dane tworzą jedną logiczną sekcję. Jeżeli części mogą pojawiać się lub ulegać awarii niezależnie, rozdziel je na osobne komponenty i granice Suspense. Gdy jedno zapytanie wymaga wyniku poprzedniego, sekwencja jest uzasadniona, ponieważ streaming może pokazać wcześniejszy etap, ale nie usunie zależności.
Suspense, cache i dane dynamiczne w Next.js 16
Samo owinięcie komponentu w <Suspense> nie powoduje streamingu. Granica pokaże fallback dopiero wtedy, gdy potomek faktycznie zawiesi renderowanie, np. czekając na asynchroniczne dane. Komponent synchroniczny zostanie wyrenderowany normalnie. W Next.js 16 wyniki fetch nie są domyślnie zapisywane w cache; świeże dane możesz streamować w czasie żądania, a stabilne dane objąć use cache, aby znalazły się w prerenderowanej powłoce.
Jeżeli używasz Cache Components, dostęp do danych runtime, takich jak cookies(), headers() lub searchParams, powinien znaleźć się pod odpowiednią granicą Suspense. Next.js może wtedy prerenderować zawartość ponad granicą, umieścić fallback w statycznej powłoce i wykonać część zależną od żądania później. Granice umieszczaj możliwie blisko wolnej sekcji, ponieważ zbyt wysoka granica zastąpi fallbackiem również treść, która mogłaby pojawić się od razu.
Błędy i kody HTTP podczas streamingu
Suspense odpowiada za stan oczekiwania, nie jest pełnym mechanizmem obsługi błędów. Dodaj error.tsx dla segmentu routingu i loguj błędy po stronie serwera. React może podczas renderowania serwerowego wysłać fallback najbliższej granicy, a następnie ponowić render problematycznego drzewa po stronie klienta; jeśli błąd wystąpi ponownie, przejmie go najbliższa Error Boundary.
Po wysłaniu pierwszego fragmentu nagłówki odpowiedzi są już zatwierdzone, więc serwer nie może zmienić kodu HTTP. Późne notFound() lub redirect() mogą przekazać odpowiednią instrukcję w streamie, ale transportowa odpowiedź może pozostać 200. Autoryzację, sprawdzenie istnienia głównego zasobu i inne decyzje wpływające na status wykonuj przed rozpoczęciem streamingu, jeżeli poprawny kod HTTP jest wymagany przez klienta API, cache lub monitoring.
Hosting i testowanie streamingu
Na hostingu własnym sprawdź cały łańcuch: serwer Next.js, reverse proxy, load balancer i CDN. Nginx oraz część integracji chmurowych może domyślnie buforować odpowiedź, przez co użytkownik dostanie powłokę i wolną sekcję jednocześnie. Next.js zaleca wyłączenie buforowania, np. nagłówkiem X-Accel-Buffering: no, oraz potwierdzenie obsługi strumieniowania HTTP/2 albo odpowiedzi chunked w HTTP/1.1.
Testuj produkcyjny build, a nie tylko next dev. W DevTools sprawdź timing odpowiedzi i moment pojawienia się kolejnych sekcji; z terminala możesz użyć curl --no-buffer URL, aby obserwować fragmenty bez lokalnego buforowania. Dodaj kontrolowane opóźnienie wyłącznie w środowisku testowym i zweryfikuj osobno czas do powłoki, czas do każdej granicy, CLS oraz zachowanie po błędzie i po przerwaniu nawigacji.
