Pobieranie danych w App Router Next.js to jeden z najbardziej mylących tematów dla developerów przechodzących z Pages Router lub innych frameworków. Zapomnij o getStaticProps i getServerSideProps — teraz masz fetch z rozszerzeniami, cache, i rewalidację. Nie wiesz, który router wybrać? Sprawdź App Router vs Pages Router.
W tym artykule wyjaśnię, jak to wszystko działa. Bez uproszczeń, z praktycznymi przykładami i wzorcami, które stosuję w produkcyjnych projektach.
Krótka odpowiedź: W Next.js 15+ fetch domyślnie NIE cachuje (zmiana względem wersji 13/14). Cachowanie jawne: cache: 'force-cache' (statyczne), cache: 'no-store' (dynamiczne), next: { revalidate: 60 } (ISR, czyli Incremental Static Regeneration, pozwala odświeżać strony statyczne po czasie bez pełnego rebuildu.). Rewalidacja na żądanie: revalidateTag(tag, 'max') lub revalidatePath(path). Dane z bazy/SDK: unstable_cache() lub nowsze use cache. Równoległe pobieranie: Promise.all(). Deduplikacja w Server Components: cache() z React. Streaming: <Suspense>.
Podstawy — fetch w Server Components
W App Router pobierasz dane bezpośrednio w komponencie. Żadnych specjalnych funkcji, żadnych hooków — po prostu async/await:
To działa, bo komponenty w App Router są domyślnie Server Components — wykonują się na serwerze, gdzie fetch jest dostępny globalnie.
Domyślne zachowanie cache
Next.js rozszerza natywny fetch o własny Data Cache. I tu zaczyna się zamieszanie, bo trzeba odróżnić cache danych od prerenderowanego HTML.
W Next.js 15+ fetch domyślnie nie zapisuje odpowiedzi do Data Cache. Jeśli chcesz cachować wynik requestu, podajesz to jawnie:
To jednak nie znaczy, że każda strona z takim fetch() będzie zawsze renderowana od zera przy każdym wejściu. Next.js nadal może prerenderować trasę i cachować HTML, jeśli cała reszta drzewa na to pozwala.
Ważne: W Next.js 13/14 domyślne zachowanie
fetchbyło inne. Jeśli czytasz starsze tutoriale, bardzo łatwo pomylić stare przykłady z aktualnym modelem App Router.
Opcje cache w fetch
Next.js dodaje do fetch opcję cache i next.revalidate:
1. Static Data
Użyj dla: treści, które rzadko się zmieniają (about page, FAQ, konfiguracja).
2. Dynamic Data
Użyj dla: danych specyficznych dla użytkownika, danych zmieniających się przy każdym żądaniu.
3. Time-based Revalidation (ISR)
Użyj dla: blogów, list produktów, treści aktualizowanych regularnie.
Rewalidacja — odświeżanie cache
Masz dwie strategie rewalidacji:
Time-based Revalidation
Cache wygasa po określonym czasie:
Jak to działa:
- Użytkownik A odwiedza stronę → dane z cache (jeśli istnieją) lub świeże
- Mija 3600 sekund
- Użytkownik B odwiedza stronę → dostaje stare dane, ale w tle Next.js pobiera nowe
- Użytkownik C odwiedza stronę → dostaje nowe dane
To tzw. "stale-while-revalidate" — użytkownik zawsze dostaje odpowiedź szybko.
On-demand Revalidation
Ręczne odświeżanie cache przez API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami.:
Wywołaj po aktualizacji danych w CMS, czyli Content Management System, to system do zarządzania treścią bez ręcznej edycji kodu.:
Taki endpoint musi być zabezpieczony tokenem albo podpisem webhooka. Bez tego otwierasz każdemu możliwość ręcznego czyszczenia cache.
Cache Tags — precyzyjna kontrola
Tagi pozwalają grupować dane i rewalidować je razem:
Jeśli wywołujesz mutację wewnątrz Server Action i chcesz od razu zobaczyć własny zapis, sprawdź też updateTag(). To bardziej precyzyjne API dla scenariusza "read your own writes".
Pobieranie danych bez fetch
Nie wszystkie dane pochodzą z HTTP API. Co z bazą danych, systemem plików, zewnętrznymi SDK?
Baza danych (Prisma, Drizzle) — sprawdź pełny tutorial Prisma + Next.js
unstable_cache nadal działa, ale w aktualnej dokumentacji Next.js jest traktowane jako starsze API. W nowych projektach warto śledzić też use cache, cacheTag() i Cache Components, bo to w tę stronę idzie framework.
Zewnętrzne SDK
Wzorce pobierania danych
Wzorzec 1: Parallel fetching
Pobieraj niezależne dane równolegle:
Wzorzec 2: Preloading
Zacznij pobierać dane wcześniej:
Wzorzec 3: Streaming z Suspense
Pokazuj treść progresywnie:
Wzorzec 4: Deduplikacja requestów
React automatycznie deduplikuje identyczne fetch w jednym renderze:
Dla funkcji innych niż fetch użyj cache z React:
Jeśli dane pochodzą z Twojej bazy lub własnego SDK, zwykle lepiej ominąć własny Route Handler i wywołać warstwę danych bezpośrednio na serwerze.
Dynamiczne vs Statyczne renderowanie
Next.js automatycznie decyduje, czy strona jest statyczna czy dynamiczna na podstawie użytych funkcji:
Strona będzie dynamiczna, gdy używasz:
fetchzcache: 'no-store'cookies(),headers()searchParamsw komponencie stronyconnection()lub innych dynamicznych API
Wymuszenie dynamicznego renderowania:
W nowszych wersjach Next.js zamiast starego unstable_noStore() częściej zobaczysz connection() albo po prostu jawne użycie cache: 'no-store' na poziomie konkretnego requestu.
Wymuszenie statycznego renderowania:
Obsługa błędów
Z error.tsx:
FAQ
Jak działa cache w fetch Next.js i co zmieniło się w wersji 15?
W Next.js 13/14 fetch domyślnie zapisywał odpowiedź do Data Cache (force-cache). W Next.js 15+ domyślne zachowanie zmieniło się na brak zapisu do Data Cache, chyba że jawnie podasz cache: 'force-cache' lub next: { revalidate: X }. To ważna zmiana powodująca problemy przy migracji — starsze tutoriale opisują inne domyślne zachowanie.
Jaka jest różnica między revalidatePath a revalidateTag?
revalidatePath('/blog') czyści cache dla konkretnej ścieżki URL — przydatne gdy chcesz odświeżyć konkretną stronę. revalidateTag('posts', 'max') czyści cache dla wszystkich requestów otagowanych danym tagiem — niezależnie od tego, które strony z nich korzystają. Tagi są bardziej precyzyjne: możesz tagować ['posts', 'post-123'] i rewalidować tylko zmieniony post bez ruszania reszty.
Co to jest ISR (Incremental Static Regeneration) i jak go używać?
ISR to strategia cachowania, w której strona jest wstępnie zrenderowana statycznie, ale odświeżana w tle po upłynięciu zadanego czasu. Konfiguracja: fetch(url, { next: { revalidate: 3600 } }) lub export const revalidate = 3600 na poziomie strony. Mechanizm stale-while-revalidate: użytkownik zawsze dostaje cached odpowiedź (szybko), a Next.js regeneruje stronę w tle gdy cache wygaśnie.
Kiedy używać cache: 'no-store', a kiedy cache: 'force-cache'?
cache: 'no-store' — dane specyficzne dla użytkownika (sesja, koszyk, profil), dane zmieniające się przy każdym żądaniu, strony wymagające świeżych danych w czasie rzeczywistym. cache: 'force-cache' — treści rzadko się zmieniające (about page, FAQ, konfiguracja), publiczne dane, które mogą być wspólne dla wszystkich użytkowników. Domyślnie (bez opcji) Next.js 15+ zachowuje się jak no-store.
Jak cachować dane z bazy danych (Prisma, Drizzle) w Next.js?
Dane z bazy nie przechodzą przez rozszerzony fetch, więc używasz unstable_cache() z Next.js lub cache() z React. Przykład: const getCachedUsers = unstable_cache(async () => db.user.findMany(), ['users'], { revalidate: 3600, tags: ['users'] }). W aktualnej dokumentacji Next.js unstable_cache jest traktowane jako starsze API — nowe projekty mogą śledzić use cache jako kierunek przyszłościowy.
Jak pobierać dane równolegle w Next.js App Router?
Używaj Promise.all() dla niezależnych requestów: const [user, posts, stats] = await Promise.all([getUser(), getPosts(), getStats()]). Sekwencyjne await (jedno po drugim) sumuje czasy oczekiwania — przy trzech niezależnych requestach po 500ms różnica wynosi 1500ms vs 500ms. Dla preloadowania danych przed renderowaniem użyj wzorca preload() z void getUser(id) wywołanego wcześniej w drzewie komponentów.
Co to jest deduplikacja requestów w Next.js i jak działa?
React automatycznie deduplikuje identyczne wywołania fetch (ten sam URL, te same opcje) w jednym cyklu renderowania — każdy z nich wykonywany jest tylko raz, nawet jeśli wiele komponentów wywołuje go niezależnie. Dla funkcji nie-fetchowych (zapytania do bazy, SDK) użyj cache() z React: export const getUser = cache(async (id) => db.user.findUnique(...)) — deduplikuje wywołania w ramach jednego renderowania serwera.
Źródła i dokumentacja
Podsumowanie — cheat sheet
| Scenariusz | Rozwiązanie |
|---|---|
| Dane statyczne (rzadko się zmieniają) | fetch({ cache: 'force-cache' }) |
| Dane dynamiczne (per-request) | fetch({ cache: 'no-store' }) |
| Dane odświeżane co X sekund | fetch({ next: { revalidate: X } }) |
| Odświeżanie po akcji (CMS, webhook) | revalidatePath() lub revalidateTag(tag, 'max') |
| Natychmiastowy odczyt po mutacji w Server Action | updateTag() |
| Dane z bazy/SDK | unstable_cache() lub nowsze use cache |
| Równoległe pobieranie | Promise.all([...]) |
| Deduplikacja | cache() z React |
| Streaming | <Suspense> |
Najczęstsze błędy
- Mieszanie cache danych z cache HTML — to dwa różne poziomy
- Zapominanie o
cache: 'no-store'dla danych użytkownika - Sekwencyjne zamiast równoległe pobieranie
- Brak obsługi błędów — zawsze sprawdzaj
res.ok - Wywoływanie własnych endpointów z Server Components bez potrzeby zamiast użycia warstwy danych
- Zbyt krótki
revalidate— obciąża API bez potrzeby - Zbyt długi
revalidate— użytkownicy widzą stare dane
Chcesz głębiej poznać cachowanie? Przeczytaj o buforowaniu z unstable_cache i Redis lub sprawdź, jak działają Server Actions — formularze bez endpointów API.
