Next.js prefetch zmierzony: Partial Prefetching w 16.3

Partial Prefetching w Next.js 16.3 zmniejszył w naszym benchmarku prefetch nawet o 93%, ale treść adresu przychodzi po kliknięciu. Kiedy co się opłaca.

Opublikowano: Czas czytania: 9 min czytania

Next.js 16.3 (otwiera się w nowej karcie) zmienia to, czym jest Next.js prefetch. Z włączonym partialPrefetching (otwiera się w nowej karcie) App Router nie pobiera już payloadu dla każdego linku na ekranie: pobiera jeden wspólny szkielet na trasę, a treść zależną od adresu zostawia na po kliknięciu. Zbudowaliśmy jedną aplikację na sześć sposobów i przeklikaliśmy ją 3744 razy, żeby sprawdzić, co ta zamiana daje i ile kosztuje.

Na stronie dokumentacji ze 100 linkami w bocznym menu prefetch zmalał o 93%. Ceną jest czas: treść, która wcześniej była na ekranie niemal od razu, przychodzi teraz kilkaset milisekund po kliknięciu, za szkieletem strony. Która strona tej zamiany jest lepsza, zależy od strony i od jednego szczegółu Reacta, który łatwo przeoczyć.

mniej bajtów prefetchu w dokumentacji
93%
mniej bajtów na całą nawigację w dokumentacji
11×
widoczny szkielet, przypięty przez Reacta
298 ms
treść na ekranie z prefetch={true}
43 ms

Co zmienia Partial Prefetching

Do wersji 16.3 App Router (otwiera się w nowej karcie) robił prefetch dla każdego linku: gdy na ekran wjeżdżało N linków, pobierał mniej więcej N payloadów. Z partialPrefetching (wymaga cacheComponents (otwiera się w nowej karcie)) pobiera jeden App Shell (otwiera się w nowej karcie) na trasę: wszystko, co strona renderuje niezależnie od adresu. Linki do tej samej trasy dzielą ten jeden prefetch. Treść, która czyta params albo searchParams, rozwiązuje się po kliknięciu, chyba że link poprosi o więcej przez prefetch={true}.

Porównaliśmy sześć buildów tej samej aplikacji:

  • L0, bez Cache Components: klasyczny App Router.
  • C0, samo Cache Components.
  • P0, Cache Components z Partial Prefetching i domyślnym inliningiem prefetchu.
  • P1–P3, to samo z wyłączonym inliningiem, z małymi progami i z dużymi.

Każdy przeklikaliśmy w trzech nawigacjach: z katalogu 48 produktów na stronę produktu, ze strony dokumentacji ze 100 linkami w menu na inną stronę dokumentacji i z wyników wyszukiwania na inne wyszukiwanie przez jeden z ośmiu linków z prefetch={true}.

Bajty prefetchu: nawet o 93% mniej

W dokumentacji samo Cache Components pobierało z wyprzedzeniem każdy widoczny link z menu. Partial Prefetching zmienił to z 402 kB29 kB−93%lepiej, w 8,5 żądaniach zamiast 49. W katalogu oszczędność wyniosła 78%. Wyszukiwanie prawie się nie zmieniło, bo jego osiem linków z prefetch={true} nadal pobiera własne wyniki.

Partial Prefetching pobiera ułamek bajtów na stronach z wieloma linkami

Skompresowane bajty RSC pobrane przed kliknięciem · komputer, bez ograniczeń, późne kliknięcie · mediany z 20 przebiegów

PozycjaKatalog → produktDokumentacja → dokumentacjaWyszukiwanie → wyszukiwanie
L0 · Klasyczny router30 kB408 kB98 kB
C0 · Cache Components160 kB402 kB163 kB
P0 · Partial Prefetching34 kB29 kB107 kB
P1 · Bez inliningu33 kB32 kB105 kB
P2 · Mały inlining32 kB28 kB103 kB
P3 · Duży inlining34 kB33 kB105 kB
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż jako obraz
Partial Prefetching pobiera ułamek bajtów na stronach z wieloma linkami. Skompresowane bajty RSC pobrane przed kliknięciem · komputer, bez ograniczeń, późne kliknięcie · mediany z 20 przebiegów

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

Jeden wiersz zasługuje na drugie spojrzenie. Włączenie Cache Components bez Partial Prefetching sprawiło, że katalog pobierał pięć razy więcej niż klasyczny router, 30 kB160 kB+426%gorzej: prerenderowane szkielety produktów da się wtedy pobrać osobno dla każdego linku. Jeśli włączasz Cache Components na stronach z wieloma linkami, od razu zdecyduj o Partial Prefetching.

Czy nawigacja jest naprawdę natychmiastowa?

Szkielet tak. Przy Partial Prefetching pojawiał się 43 ms45 ms po kliknięciu na zwolnionym telefonie. Z tym, po co ktoś kliknął, czyli produktem albo artykułem, jest inaczej: potrzebuje żądania po kliknięciu.

Samo Cache Components pokazuje treść od razu, Partial Prefetching po jednym żądaniu

Od kliknięcia do pierwszej klatki z treścią zależną od adresu · telefon, ograniczenie Lighthouse mobile, późne kliknięcie · ms

  • L0 · Klasyczny router
  • C0 · Cache Components
  • P0 · Partial Prefetching
Katalog → produkt375 ms · 60 ms · 262 ms
Dokumentacja → dokumentacja47 ms · 52 ms · 345 ms
Wyszukiwanie → wyszukiwanie40 ms · 41 ms · 43 ms
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
PozycjaL0 · Klasyczny routerC0 · Cache ComponentsP0 · Partial Prefetching
Katalog → produkt375 ms60 ms262 ms
Dokumentacja → dokumentacja47 ms52 ms345 ms
Wyszukiwanie → wyszukiwanie40 ms41 ms43 ms
Samo Cache Components pokazuje treść od razu, Partial Prefetching po jednym żądaniu. Od kliknięcia do pierwszej klatki z treścią zależną od adresu · telefon, ograniczenie Lighthouse mobile, późne kliknięcie · ms

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

W dokumentacji samo Cache Components pokazało nowy artykuł po 52 ms, Partial Prefetching po 345 ms. Na stronie produktu różnica była mniejsza, 60 ms262 ms+337%gorzej, a najwolniejszy był klasyczny router, bo dla dynamicznej trasy nie miał nic wyrenderowanego z góry. Wyszukiwanie, gdzie każdy link ma prefetch={true}, pokazywało treść po 40 ms43 ms w każdej konfiguracji.

Szkielet, który zobaczysz: 300 ms

Na stronie produktu trzy etapy przychodzą po kolei: szkielet, produkt, a potem część renderowana w chwili żądania.

Przy Partial Prefetching strona przychodzi w trzech krokach

Katalog → produkt · telefon, zwolniony, późne kliknięcie · od kliknięcia do każdego etapu na ekranie · ms

  • Szkielet
  • Treść
  • Część z serwera
L0 · Klasyczny router375 ms · 375 ms · 375 ms
C0 · Cache Components60 ms · 60 ms · 342 ms
P0 · Partial Prefetching47 ms · 262 ms · 562 ms
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
PozycjaSzkieletTreśćCzęść z serwera
L0 · Klasyczny router375 ms375 ms375 ms
C0 · Cache Components60 ms60 ms342 ms
P0 · Partial Prefetching47 ms262 ms562 ms
Przy Partial Prefetching strona przychodzi w trzech krokach. Katalog → produkt · telefon, zwolniony, późne kliknięcie · od kliknięcia do każdego etapu na ekranie · ms

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

W dokumentacji szkielet był widoczny przez 298 ms w każdym buildzie z Partial Prefetching. To nie jest czas sieci: odpowiedź dochodziła po 250 ms.

Wczesne kliknięcie

Kliknięcie 50 ms po hydratacji, zanim skończy się jakikolwiek prefetch, zabiera przewagę wszystkim, którzy pobierają z wyprzedzeniem. Samo Cache Components jest wtedy mniej więcej tak samo wolne jak Partial Prefetching, bo obie konfiguracje najpierw pokazują fallback, a potem odczekują 300 ms.

Przy wczesnym kliknięciu żadna konfiguracja niczego jeszcze nie pobrała

Kliknięcie 50 ms po hydratacji · telefon, zwolniony · od kliknięcia do treści · ms

  • L0 · Klasyczny router
  • C0 · Cache Components
  • P0 · Partial Prefetching
Katalog → produkt367 ms · 374 ms · 389 ms
Dokumentacja → dokumentacja262 ms · 528 ms · 529 ms
Wyszukiwanie → wyszukiwanie234 ms · 404 ms · 339 ms
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
PozycjaL0 · Klasyczny routerC0 · Cache ComponentsP0 · Partial Prefetching
Katalog → produkt367 ms374 ms389 ms
Dokumentacja → dokumentacja262 ms528 ms529 ms
Wyszukiwanie → wyszukiwanie234 ms404 ms339 ms
Przy wczesnym kliknięciu żadna konfiguracja niczego jeszcze nie pobrała. Kliknięcie 50 ms po hydratacji · telefon, zwolniony · od kliknięcia do treści · ms

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

Ostatni etap, czyli część z serwera, dzieli te kliknięcia na dwie grupy. Przy Partial Prefetching w 612 wczesnych kliknięć na stronie produktu cała strona była na ekranie po 375 ms400 ms. W pozostałych 6 dopiero po 650 ms700 ms, a pomiędzy nie trafił żaden przebieg. Ta przerwa to mniej więcej 300 ms, które React odczekuje między dwoma odsłonięciami: ostatni etap pojawia się razem z produktem albo jedno odczekanie później.

Ostatni etap przychodzi razem z produktem albo mniej więcej 300 ms później

Katalog → produkt, wczesne kliknięcie · Partial Prefetching · telefon, zwolniony, przebieg HTTP/2 · od kliknięcia do ostatniego etapu na ekranie · ms

Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
Zakresprzebiegi
375 ms–400 ms6
400 ms–425 ms0
425 ms–450 ms0
450 ms–475 ms0
475 ms–500 ms0
500 ms–525 ms0
525 ms–550 ms0
550 ms–575 ms0
575 ms–600 ms0
600 ms–625 ms0
625 ms–650 ms0
650 ms–675 ms3
675 ms–700 ms3
Ostatni etap przychodzi razem z produktem albo mniej więcej 300 ms później. Katalog → produkt, wczesne kliknięcie · Partial Prefetching · telefon, zwolniony, przebieg HTTP/2 · od kliknięcia do ostatniego etapu na ekranie · ms

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

Cały rachunek

Partial Prefetching przenosi bajty sprzed kliknięcia na po kliknięciu, a na stronie produktu dodatkowo je podwaja: odpowiedź nawigacji niosła dwie pełne kopie payloadu produktu, 13 kB26 kB+99%gorzej po kompresji. Licząc wszystko, co pobiera nawigacja, razem z prefetchem, w dokumentacji i tak wychodzi daleko do przodu.

Cała nawigacja w dokumentacji kosztuje mniej więcej jedną dziesiątą bajtów

Wszystkie skompresowane bajty RSC jednej nawigacji w dokumentacji, prefetch i nawigacja · telefon, zwolniony, późne kliknięcie

L0 · Klasyczny router463 kB
C0 · Cache Components458 kB
P0 · Partial Prefetching40 kB
P1 · Bez inliningu43 kB
P2 · Mały inlining39 kB
P3 · Duży inlining44 kB
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
PozycjaWartośćn
L0 · Klasyczny router463 kB20
C0 · Cache Components458 kB20
P0 · Partial Prefetching40 kB20
P1 · Bez inliningu43 kB20
P2 · Mały inlining39 kB20
P3 · Duży inlining44 kB20
Cała nawigacja w dokumentacji kosztuje mniej więcej jedną dziesiątą bajtów. Wszystkie skompresowane bajty RSC jednej nawigacji w dokumentacji, prefetch i nawigacja · telefon, zwolniony, późne kliknięcie

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

Mniej więcej 11 razy mniej bajtów w dokumentacji i 2,2 raza mniej w katalogu. Hydratacja i opóźnienie reakcji na stronie startowej nie zmieniły się w żadnej konfiguracji.

Kiedy prefetch={true} się opłaca

<Link prefetch={true}> (otwiera się w nowej karcie) pobiera też dane z adresu strony docelowej, więc kliknięcie jest tak samo natychmiastowe jak przy pełnym prefetchu. Płacisz za każdy widoczny link: osiem linków wyszukiwania kosztowało 107 kB, więcej niż cała strona dokumentacji przy Partial Prefetching. Używaj go tam, gdzie kliknięcie jest prawdopodobne, a dane da się cache'ować: podpowiedzi wyszukiwania, link „dalej”, kilka pierwszych wyników. Na siatkach kart rób prefetch na zamiar (najechanie albo dotknięcie), zamiast oznaczać każdą kartę.

prefetchInlining: zostaw włączony

experimental.prefetchInlining (otwiera się w nowej karcie) łączy małe odpowiedzi prefetchu w jedną. Ustawienie domyślne i nasze dwa zestawy progów różniły się najwyżej jednym żądaniem i kilkoma kilobajtami, bez mierzalnej różnicy w czasie.

Żądania i skompresowane bajty prefetchu według ustawienia inliningu · komputer, bez ograniczeń, późne kliknięcie · mediany
KonfiguracjaScenariuszŻądaniaPobrane z wyprzedzeniem
P0 · Partial PrefetchingKatalog → produkt1134,4 kB
P0 · Partial PrefetchingDokumentacja → dokumentacja8,529,4 kB
P0 · Partial PrefetchingWyszukiwanie → wyszukiwanie17106,5 kB
P1 · Bez inlininguKatalog → produkt1633,4 kB
P1 · Bez inlininguDokumentacja → dokumentacja732 kB
P1 · Bez inlininguWyszukiwanie → wyszukiwanie23105 kB
P2 · Mały inliningKatalog → produkt1131,8 kB
P2 · Mały inliningDokumentacja → dokumentacja727,9 kB
P2 · Mały inliningWyszukiwanie → wyszukiwanie17102,8 kB
P3 · Duży inliningKatalog → produkt1033,8 kB
P3 · Duży inliningDokumentacja → dokumentacja733,1 kB
P3 · Duży inliningWyszukiwanie → wyszukiwanie16105,1 kB
Pobierz CSV

Wyłączenie inliningu dodało pięć albo sześć żądań w katalogu i wyszukiwaniu, a przez HTTP/1.1 kosztowało to wczesne kliknięcie w katalogu: sześć połączeń na host się zapycha. Przez HTTP/2 (otwiera się w nowej karcie) kara znikała. Wszystkie inne konfiguracje wypadły na obu protokołach tak samo.

HTTP/2 ma znaczenie tylko przy wyłączonym inliningu

Katalog → produkt, wczesne kliknięcie · telefon, zwolniony · od kliknięcia do treści · HTTP/1.1 i HTTP/2 na przemian, po 12 przebiegów · ms

  • HTTP/1.1
  • HTTP/2
L0 · Klasyczny router365 ms · 363 ms
C0 · Cache Components375 ms · 373 ms
P0 · Partial Prefetching387 ms · 386 ms
P1 · Bez inliningu440 ms · 372 ms
P2 · Mały inlining390 ms · 383 ms
P3 · Duży inlining387 ms · 386 ms
Źródło: Benchmark Outofplace, Next.js 16.3.6Pobierz CSVPobierz PNG
Pokaż dane
PozycjaHTTP/1.1HTTP/2
L0 · Klasyczny router365 ms363 ms
C0 · Cache Components375 ms373 ms
P0 · Partial Prefetching387 ms386 ms
P1 · Bez inliningu440 ms372 ms
P2 · Mały inlining390 ms383 ms
P3 · Duży inlining387 ms386 ms
HTTP/2 ma znaczenie tylko przy wyłączonym inliningu. Katalog → produkt, wczesne kliknięcie · telefon, zwolniony · od kliknięcia do treści · HTTP/1.1 i HTTP/2 na przemian, po 12 przebiegów · ms

Wykres jako obraz. Możesz go użyć na licencji CC BY 4.0, podając Outofplace jako źródło i link do tej strony.

Którą konfigurację wybrać

Samo Cache Components

  • Treść adresu od razu po późnym kliknięciu
  • Jeden prefetch na każdy widoczny link
  • Setki kilobajtów na stronach z wieloma linkami

Z Partial Prefetching

  • Szkielet od razu, treść adresu po jednym żądaniu
  • Jeden prefetch na trasę
  • O 78–93% mniej bajtów prefetchu tam, gdzie linków jest dużo
next.config.ts
const nextConfig: NextConfig = {
  cacheComponents: true,
  // Jeden App Shell na trasę zamiast prefetchu na każdy link.
  partialPrefetching: true,
};
Linki, przy których czekanie by bolało
<Link href={`/search?q=${suggestion}`} prefetch={true}>
  {suggestion}
</Link>
  1. Policz linki

    Najwięcej zyskują strony z wieloma linkami do tej samej dynamicznej trasy: dokumentacja, siatki, feedy. Strona z kilkoma linkami do różnych stron zyskuje niewiele.

  2. Zdecyduj dla każdej trasy

    Tam, gdzie natychmiastowa treść jest ważniejsza niż bajty, a strony są statyczne, zostaw pełny prefetch; gdzie indziej włącz Partial Prefetching. Konfiguracja segmentu export const prefetch = 'partial' (otwiera się w nowej karcie) pozwala przechodzić trasa po trasie.

  3. Oznacz prawdopodobne kliknięcia

    Dodaj prefetch={true} tam, gdzie następne kliknięcie da się przewidzieć, a dane da się cache'ować.

  4. Zaprojektuj szkielet

    Na wolnym łączu będzie widoczny przez około 300 ms. Tytuł i nawigację umieść w części niezależnej od adresu: pojawia się niemal od razu.

Ta strona działa na Partial Prefetching: większość naszych stron ma mało linków, a te, które mają ich dużo (blog, case studies), są statyczne i lekkie. Nasze własne produkty, Portivo, Sprawna MaturaLevera, też stoją na Next.js.

Jak mierzyliśmy

Czego te dane nie mówią

To laboratorium: jedna maszyna, localhost, bez CDN, bez obrazów i fontów, wygenerowany tekst i sztuczne 300 ms opóźnienia po stronie serwera. Ograniczanie działa per żądanie, nie na poziomie pakietów, więc czasy bezwzględne są optymistyczne; liczą się porównania. Późne kliknięcie to najlepszy przypadek dla prefetchu, a wczesne to jeden konkretny wyścig; prawdziwi użytkownicy klikają wszędzie pomiędzy. Mechanika App Shella, podwojony payload produktu i 300 ms odsłonięcia dotyczą Next.js 16.3.6 z jego wersją canary Reacta i mogą się zmienić w wydaniu poprawkowym.

App Shell przychodzi przez prawdziwy link

Wspólny szkielet jest pobierany przez adres tego linku, który router zaplanuje jako pierwszy, a ta odpowiedź niesie całą stronę dla tego adresu. Kliknięcie dokładnie w ten link nie potrzebuje żadnego żądania. Cele kliknięć wybraliśmy niżej na stronie, żeby to nie podbijało wyników Partial Prefetching.

Co to jest prefetch w Next.js?

Kiedy <Link> pojawia się na ekranie, App Router w tle pobiera payload React Server Components strony docelowej, więc po kliknięciu nie trzeba czekać na sieć. Next.js 16.3 dodaje Partial Prefetching, który pobiera jeden wspólny szkielet na trasę zamiast payloadu na każdy link.

Czy nawigacja w Next.js z Partial Prefetching jest natychmiastowa?

Szkielet tak: w naszym benchmarku pojawiał się po około 45 ms na zwolnionym telefonie. Treść zależna od adresu przychodzi po kliknięciu: po około 262 ms na stronie produktu i 345 ms w dokumentacji, gdzie samo Cache Components pokazywało ją po około 52 ms. Linki z prefetch={true} zostają natychmiastowe.

Czy wyłączyć prefetch w Next.js?

Zwykle nie. Prefetch przyspieszał kliknięcie nawet o 300 ms i nie spowalniał hydratacji ani reakcji na wejście. Jeśli problemem jest ruch z prefetchu, Partial Prefetching tnie go nawet o 93% na stronach z wieloma linkami, nie rezygnując ze szkieletu.

Czym Partial Prefetching różni się od Partial Prerendering?

Partial Prerendering decyduje, co serwer renderuje z wyprzedzeniem: statyczny szkielet z dynamicznymi dziurami. Partial Prefetching decyduje, co przeglądarka pobiera przed kliknięciem: jeden szkielet na trasę, a dane z adresu po nawigacji. Partial Prefetching wymaga Cache Components.

Czy HTTP/2 ma znaczenie dla prefetchu w Next.js?

Niewielkie, dopóki inlining prefetchu jest włączony (domyślnie). W naszym teście HTTP/2 jedyna duża różnica wyszła przy wyłączonym inliningu i wczesnym kliknięciu: HTTP/2 usunął 68 ms kary, którą powodował limit sześciu połączeń na host w HTTP/1.1.

Dalsza lektura

Next.js: Adopting Partial Prefetching (otwiera się w nowej karcie)Jak działa App Shell i jak przenosić trasy jedna po drugiej.nextjs.org
Next.js: Optimizing prefetching (otwiera się w nowej karcie)Prefetch per link przez prefetch={true} i kiedy go używać.nextjs.org
Next.js: prefetchInlining (otwiera się w nowej karcie)Co łączy inlining i dlaczego domyślne ustawienie pasuje większości aplikacji.nextjs.org

Napisz, co budujemy.

Odpowiadamy w ciągu jednego dnia roboczego.