Next.js App Router ma wbudowany system cachowania. Rozszerzony fetch, unstable_cache, automatyczna deduplikacja i nowsze Cache Components robią sporo za Ciebie. Ale czy to wystarczy? Kiedy warto sięgnąć po zewnętrzny Redis?
W tym artykule porównam oba podejścia, ale od razu doprecyzuję jedną rzecz: w Next.js 16 unstable_cache jest już API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. legacy i długofalowo warto patrzeć też w stronę use cache oraz Cache Components. Mimo to nadal spotkasz je w realnych projektach, więc trzeba rozumieć oba światy.
Krótka odpowiedź: Next.js oferuje wbudowany system cachowania (rozszerzony fetch, unstable_cache, a po włączeniu Cache Components także use cache) odpowiedni dla prostych projektów i platform takich jak Vercel. Redis sprawdza się tam, gdzie potrzebujesz współdzielonego cache między wieloma instancjami, zaawansowanych operacji (sorted sets, pub/sub) lub środowisk self-hosted. W praktyce najlepszym rozwiązaniem bywa architektura hybrydowa: wbudowany cache jako szybka warstwa lokalna i Redis jako współdzielony magazyn globalny.
Wbudowany cache w Next.js
Next.js oferuje kilka mechanizmów cachowania:
1. Rozszerzony fetch
Code
// Domyślnie: bez Data Cacheconst data = await fetch('https://api.example.com/posts')// Rewalidacja co 60 sekundconst data = await fetch('https://api.example.com/posts', { next: { revalidate: 60 },})// Bez cacheconst data = await fetch('https://api.example.com/posts', { cache: 'no-store',})
2. unstable_cache
Dla danych z bazy, SDK, lub innych źródeł nie-HTTP:
W nowych projektach myśl o tym jako o przejściowym API. Nadal jest użyteczne, ale dokumentacja Next.js 16 wprost wskazuje use cache jako kierunek docelowy.
3. React cache
Deduplikacja w ramach jednego renderowania:
Code
import { cache } from 'react'export const getUser = cache(async (id: string) => { return db.user.findUnique({ where: { id } })})// Wielokrotne wywołania w jednym renderze = jeden query
Gdzie naprawdę żyje cache Next.js?
To kluczowe pytanie, bo tu łatwo o zbyt proste odpowiedzi.
W produkcji fetch(..., { cache: 'force-cache' }) i unstable_cache() korzystają z Data Cache Next.js, które potrafi przetrwać między requestami, a nawet deploymentami. To nie jest wyłącznie zwykła pamięć procesu. Jednocześnie dokładna topologia tego cache zależy od platformy i regionu, więc nie zakładaj globalnie idealnej spójności bez sprawdzenia infrastruktury.
W praktyce:
development zachowuje się inaczej niż produkcja i potrafi trzymać odpowiedzi także przez HMR,
Vercel dobrze integruje się z Data Cache, ale nadal myśl kategoriami regionów i opóźnień propagacji,
self-hosted może wymagać świadomej strategii, jeśli potrzebujesz współdzielonego cache między instancjami.
To właśnie tutaj Redis zaczyna mieć sens: nie dlatego, że wbudowany cache Next.js jest "fałszywy", ale dlatego, że Redis daje Ci własny, współdzielony i łatwiejszy do obserwowania magazyn danych.
Redis jako cache
Redis to zewnętrzny, współdzielony cache:
Code
Użytkownik A → Instancja 1 → Redis MISS → Fetch → Redis WRITE
Użytkownik B → Instancja 2 → Redis HIT → Zwrot danych (bez fetch!)
Sprawdź Redis (globalny) → HIT = zwróć + zapisz lokalnie
MISS = pobierz z bazy → zapisz w Redis → zapisz lokalnie
Rewalidacja
unstable_cache — wbudowane narzędzia
Code
import { revalidateTag, revalidatePath } from 'next/cache'// Rewaliduj wszystko z tagiem 'posts'revalidateTag('posts', 'max')// Rewaliduj konkretną ścieżkęrevalidatePath('/blog')
Redis — manualna kontrola
Code
// lib/cache.tsexport async function invalidatePattern(pattern: string): Promise<number> { const keys = await redis.keys(pattern) if (keys.length === 0) return 0 await redis.del(...keys) return keys.length}// Użycieawait invalidatePattern('posts:*') // wszystkie postyawait invalidatePattern('user:123:*') // wszystko dla usera 123
Hybrydowa rewalidacja
Code
// app/actions/posts.ts'use server'import { revalidateTag } from 'next/cache'import { redis } from '@/lib/redis'export async function createPost(data: PostData) { const post = await db.post.create({ data }) // Rewaliduj oba poziomy cache revalidateTag('posts', 'max') // Data Cache / unstable_cache await redis.del('posts:all', 'posts:popular') // Redis return post}
Benchmarki
Testowałem na prostym scenariuszu: pobierz 100 postów z PostgreSQL. Traktuj te liczby jako orientacyjne. Wyniki mocno zależą od hostingu, regionu, warm/cold startów i tego, czy mierzysz lokalny development, czy produkcję.
Metoda
Pierwszy request
Cache hit
Bez cache
~150ms
N/A
unstable_cache
~150ms
~1ms
Redis (Upstash)
~150ms
~8ms
Hybrid
~150ms
~1ms (local) / ~8ms (Redis)
Wnioski:
wbudowany cache Next.js zwykle wygrywa na czystej latencji przy HIT,
Redis wygrywa przewidywalnością i współdzieleniem między instancjami,
hybryda ma sens dopiero wtedy, gdy naprawdę rozumiesz dwa poziomy invalidation.
Typowe błędy
1. Cache bez TTL
Code
// ❌ Źle — dane nigdy nie wygasająawait redis.set('posts', data)// ✅ Dobrze — zawsze ustaw TTLawait redis.setex('posts', 3600, data)
2. Brak obsługi cache miss
Code
// ❌ Źle — zakłada że cache zawsze istniejeconst data = await redis.get('key')return data.items // 💥 Error jeśli null// ✅ Dobrze — obsłuż nullconst data = await redis.get('key')if (!data) { return fetchFreshData()}return data
3. Cache stampede
Gdy cache wygaśnie, wszystkie requesty jednocześnie pobierają dane:
Code
// ✅ Rozwiązanie: lockexport async function cachedWithLock<T>( key: string, fn: () => Promise<T>, ttl: number,): Promise<T> { const cached = await redis.get<T>(key) if (cached) return cached const lockKey = `lock:${key}` // SET NX EX — atomowe: ustaw jeśli nie istnieje + wygaśnięcie w jednej komendzie const acquired = await redis.set(lockKey, '1', { nx: true, ex: 10 }) if (acquired) { try { const data = await fn() await redis.setex(key, ttl, data) return data } finally { await redis.del(lockKey) } } // Ktoś inny pobiera — czekaj i sprawdź ponownie await new Promise((r) => setTimeout(r, 100)) return cachedWithLock(key, fn, ttl)}
FAQ
Czym różni się unstable_cache od use cache w Next.js?
unstable_cache to starsze API, nadal dostępne w Next.js 16, ale oznaczone jako legacy. use cache to nowszy kierunek, który Next.js promuje jako docelowy sposób na cachowanie danych w App Router, przy czym działa w modelu Cache Components i wymaga jego świadomego włączenia. W nowych projektach warto patrzeć w tę stronę, natomiast w projektach, które już korzystają z unstable_cache, nie ma pilnej potrzeby migracji tylko dlatego, że API zmieniło status.
Kiedy Redis jest lepszy niż wbudowany cache Next.js?
Redis jest lepszym wyborem, gdy Twoja aplikacja działa na wielu instancjach serwera i wymaga wspólnego stanu cache, gdy hostingujesz ją samodzielnie poza Vercel, lub gdy potrzebujesz zaawansowanych operacji takich jak sorted sets, pub/sub, leaderboardy czy sesje użytkowników z kontrolowanym TTL.
Czy Redis spowalnia aplikację?
Redis wprowadza opóźnienie sieciowe (zwykle kilka–kilkanaście ms), więc cache hit z Redisa jest wolniejszy niż hit z pamięci procesu. Jednak przy wielu instancjach bez Redis każda może mieć własne, niespójne dane. Dla większości aplikacji produkcyjnych opóźnienie Redisa jest akceptowalne, a korzyść ze spójności danych przeważa.
Co to jest cache stampede i jak go uniknąć?
Cache stampede to sytuacja, gdy wiele równoczesnych requestów trafia na wygasły klucz cache i wszystkie jednocześnie próbują pobrać dane ze źródła. Rozwiązaniem jest blokada (lock) z atomową operacją SET NX EX w Redis: tylko jeden request pobiera dane i zapisuje je do cache, pozostałe czekają i korzystają z gotowego wyniku.
Czy powinienem cachować wszystkie zapytania do bazy danych?
Nie. Cachowanie ma sens dla danych, które często się powtarzają i rzadko się zmieniają. Dane sesji użytkownika, konfiguracja aplikacji czy popularne wpisy blogowe to dobry kandydat. Dane wrażliwe na świeżość (np. stan zamówienia w trakcie realizacji) lub unikalne dla każdego żądania najczęściej nie powinny być agresywnie cachowane.
Jak poprawnie unieważniać cache w architekturze hybrydowej?
W modelu hybrydowym (unstable_cache + Redis) trzeba invalidować oba poziomy jednocześnie. W Server Actions lub route handlerach wywołaj revalidateTag() dla warstwy Next.js i redis.del() dla kluczy Redis. Pominięcie jednego poziomu może prowadzić do sytuacji, gdzie jeden poziom serwuje nieaktualne dane.
Czy Redis Upstash sprawdza się na produkcji?
Upstash to Serverless to model uruchamiania kodu, w którym nie zarządzasz ręcznie serwerem, a płacisz zwykle za wykonania lub użycie. Redis dostępny przez HTTP, co czyni go prostym w integracji z Next.js (szczególnie na Vercel). Sprawdza się dobrze przy umiarkowanym ruchu i projektach, które nie chcą zarządzać własnym serwerem Redis. Przy bardzo wysokim natężeniu requestów lub wymaganiach nisko-latencyjnych warto rozważyć self-hosted Redis lub dedykowaną ofertę Redis Cloud z niską latencją geograficzną.
Podsumowanie — decision tree
Code
Czy potrzebujesz współdzielonego cache między instancjami?
├─ NIE → `unstable_cache` / `use cache`
└─ TAK → Czy to self-hosted / nie-Vercel?
├─ TAK → Redis
└─ NIE → Czy potrzebujesz zaawansowanych operacji (sorted sets, pub/sub)?
├─ TAK → Redis
└─ NIE → `unstable_cache` / `use cache` (lub hybryda, jeśli masz konkretny powód)
Moja praktyka:
Proste projekty z App Router → najpierw wbudowany cache Next.js
Aplikacje z realnym ruchem i wieloma instancjami → rozważ Redis lub model hybrydowy
Self-hosted z wymaganiem współdzielenia cache → Redis jako primary cache bywa bezpieczniejszym wyborem
Masz problemy z wydajnością aplikacji Next.js? Skontaktuj się ze mną — pomogę zoptymalizować strategię cachowania dla Twojego przypadku.
Pracuję z tym zawodowo.
Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.
Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.
Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.
Anthropic uderza w Figmę i Adobe — oto Claude Design
Anthropic wypuścił właśnie narzędzie AI do tworzenia stron, landing page'ów i prezentacji z promptu. Oto co wiemy o Claude Design i Opus 4.7 — i co to oznacza dla developerów.
Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?
Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.
WordPress → Next.js — migracja treści, redirecty 301 i zachowanie pozycji SEO
Jak przenieść stronę z WordPress na Next.js bez utraty pozycji w Google? Eksport treści, mapowanie URL, redirecty 301, migracja obrazów i weryfikacja indeksacji.