Porównam tu cache Next.js z Redisem. W Next.js 16 unstable_cache ma już status legacy, ale nadal spotkasz je w projektach. Nowy kierunek to use cache i Cache Components.
Wbudowany cache w Next.js
W App Routerze masz kilka warstw, które nie robią tego samego, więc nie wrzucajmy ich do jednego worka.
1. Rozszerzony fetch w Next.js
2. unstable_cache w Next.js
Przy danych z bazy, SDK albo innego źródła nie-HTTP wygląda to tak:
unstable_cache uwzględnia argumenty funkcji w kluczu. keyParts służy do dodatkowych fragmentów niewidocznych w argumentach, np. wartości zamkniętych w closure — komentarz „klucz cache” jest więc uproszczeniem. Nie odczytuj cookies() ani headers() wewnątrz cache'owanej funkcji; pobierz potrzebną wartość wcześniej i przekaż jawnie jako argument. Dokumentacja Next.js 16 mówi wprost, że to API zostało zastąpione przez use cache.
3. use cache i Cache Components w Next.js 16
Najpierw włącz nowy model:
Następnie oznacz funkcję, komponent albo cały plik jako cache'owany:
Argumenty i odwołania do zamkniętych wartości współtworzą klucz. Wynik musi być serializowalny. Scope cache nie może bezpośrednio czytać request-time APIs takich jak cookies(), headers() czy searchParams; wartości zależne od żądania przekaż jako argument albo pozostaw poza współdzielonym cache. Uważaj zwłaszcza na identyfikator użytkownika i tenant — pominięcie ich w kluczu może ujawnić dane między odbiorcami.
Domyślny handler runtime to LRU w pamięci procesu. Przy samodzielnym hostowaniu wpisy mogą przetrwać kolejne requesty tego procesu, ale znikają po restarcie. W kolejne requesty mogą trafić do różnych instancji, więc runtime cache zwykle nie jest wspólnym, trwałym magazynem. Build-time cache i integracja platformy mogą zachowywać się inaczej — sprawdź dokumentację hostingu.
4. React cache
Deduplikacja w ramach jednego renderowania:
React cache służy do memoizacji i deduplikacji pracy podczas renderowania po stronie serwera; nie traktuj go jako trwałego cache między niezależnymi requestami ani zamiennika Redis. W development zachowanie może być dodatkowo zaciemnione przez cache Next.js.
Gdzie działa cache Next.js?
Tutaj sytuacja staje się bardziej złożona, ponieważ może się wydawać, że Next.js w prosty sposób zarządza pamięcią podręczną. Tak nie jest co pokazuje środowisko produkcyjne.
Data Cache Next.js
W środowisku produkcyjnym fetch(..., { cache: 'force-cache' }) oraz unstable_cache() wykorzystują Data Cache Next.js. Ten mechanizm buforowania jest w stanie przetrwać wiele żądań, a niekiedy nawet kolejne wdrożenia. Nie jest to jednak zwykła pamięć procesu, ponieważ jego topologia jest zależna od platformy, regionu oraz sposobu hostingu. Zatem, jeśli założymy globalnie spójny cache bez wcześniejszego sprawdzenia infrastruktury, ryzykujemy napotkanie różnych nieoczekiwanych błędów.
W praktyce wygląda to tak:
- development zachowuje się inaczej niż produkcja i potrafi trzymać odpowiedzi także przez HMR,
- Vercel dobrze integruje się z Data Cache, ale nadal myśli kategoriami regionów i opóźnień propagacji,
- samodzielne hostowanie może wymagać świadomej strategii, jeśli potrzebujesz współdzielonego cache między instancjami.
Właśnie w tym kontekście Redis zyskuje na znaczeniu. Nie wynika to z niedoskonałości wbudowanego cache'u Next.js, lecz z faktu, że Redis zapewnia własny, współdzielony magazyn, który jest łatwiejszy do inspekcji, gdy pojawiają się nieoczekiwane zachowania.
Redis jako cacheHandler, a nie osobna warstwa aplikacyjna
Next.js 16 pozwala skonfigurować własny backend dla dyrektywy cache. To ważna trzecia opcja między lokalnym LRU a ręcznym redis.get() w każdej funkcji:
Handler musi implementować kontrakt Next.js dotyczący odczytu, zapisu, wygaśnięcia i tagów; nie wystarczy podmienić Map na dwa wywołania Redisa. Użyj implementacji dostawcy albo napisz testy kontraktowe, współbieżności i invalidacji. Zaletą jest zachowanie cacheTag i semantyki Cache Components. Wadą pozostaje sieciowy roundtrip oraz koszt i dostępność zewnętrznego magazynu.
Możesz jednocześnie używać zwykłego 'use cache' dla lokalnego LRU i 'use cache: remote' dla danych wymagających współdzielenia. To nadal cache wielopoziomowy, ale granice są jawne w kodzie, a Next.js zarządza kluczami i tagami.
Redis jako zewnętrzny cache
Redis działa jako zewnętrzny cache, do którego zagląda każda instancja aplikacji:
Efekt jest prosty: instancja 1 zapisuje dane, instancja 2 może je odczytać. Bez zgadywania, co akurat trzyma lokalna warstwa.
Setup Redis z Upstash
Klucz odczytu i zapisu musi mieć ten sam namespace, więc buduj go w jednej funkcji, np. v1:${tenantId}:posts:${filterHash}. Wersja umożliwia natychmiastowe logiczne unieważnienie całej rodziny bez skanowania keyspace. Nie wkładaj do klucza surowych tokenów, e-maili ani innych danych osobowych, ponieważ klucze są widoczne w narzędziach administracyjnych i logach.
Powyższy helper wymaga jeszcze decyzji o awarii i dla treści publicznej błąd GET można potraktować jako miss i pobrać bazę, a błąd SET zalogować bez psucia odpowiedzi. Dla sesji, limitu żądań lub blokady takie „fail open” może być błędem bezpieczeństwa. Zachowanie ustal per przypadek, dodaj timeout i telemetrykę hit/miss/error.
unstable_cache kontra Redis
| Aspekt | Cache Next.js | Redis |
|---|---|---|
| API | use cache; legacy unstable_cache | Komendy ręczne lub cacheHandler |
| Współdzielenie | use cache: lokalne domyślnie; unstable_cache: zależne od Data Cache/platformy | Wspólne dla klientów tej bazy |
| Latencja | use cache: pamięć procesu; unstable_cache: zależna od Data Cache/platformy | Roundtrip sieciowy |
| Trwałość | use cache: proces; unstable_cache: zależna od Data Cache/platformy | Zależna od konfiguracji persistence/TTL |
| Invalidacja | Tagi, profile i ścieżki Next.js | DEL, wersje, indeksy lub własne eventy |
| Typy danych | Serializowalne wyniki renderowania | Strings, hashes, sets, sorted sets itd. |
| Koszt | Pamięć/limity platformy | Usługa, transfer, komendy, operacje |
| Obserwowalność | Zależna od hostingu | Zależna od dostawcy i telemetryki app |
Kiedy zostać przy cache Next.js?
Zostań przy cache Next.js, gdy problem jest prosty i siedzi blisko renderowania:
- Dane rzadko się zmieniające — konfiguracja, tłumaczenia, statyczne treści
- Hosting zapewnia semantykę cache, której potrzebujesz — albo lokalność wpisów jest akceptowalna
- Brak potrzeby dokładania osobnej usługi — mniej rzeczy do utrzymania
- Rewalidacja blisko Next.js — tagi, ścieżki i renderowanie w jednym modelu
Statycznych modułów dołączonych do bundla, takich jak lokalne pliki tłumaczeń, zwykle nie trzeba dodatkowo wkładać do runtime cache. Cache ma sens dla kosztownego odczytu lub obliczenia, nie dla każdego import.
Kiedy dodać Redis do cache w Next.js?
Redis dorzucam dopiero wtedy, gdy wbudowany cache zaczyna robić się za mało przewidywalny:
- Dane współdzielone między instancjami. Liczniki, leaderboardy, blokady; sesje traktuj jako osobny state store.
- Wiele procesów lub regionów. Domyślny lokalny LRU nie zapewnia wspólnego widoku.
- Kontrolowana persistence i retencja. Jeśli dostawca, eviction policy i backupy są tak skonfigurowane.
- Operacje typowe dla Redisa. Sorted sets, pub/sub, TTL per klucz
- Debugowanie. Chcesz zobaczyć, co naprawdę siedzi w cache.
Hybrydowy cache Next.js i Redis
W produkcji można połączyć cache Next.js z Redisem. W Next.js 16 najpierw sprawdź, czy wystarczy use cache: remote z handlerem Redis — zachowuje jeden model kluczy i tagów. Poniższa ręczna hybryda zagnieżdża Redis pod Data Cache, ale nie gwarantuje lokalnego L1, ponieważ topologię warstwy Next.js określa hosting. Stosuj ją tylko wtedy, gdy świadomie potrzebujesz dwóch niezależnych TTL i akceptujesz okno niespójności.
Przepływ jest wtedy taki:
- Request trafia do instancji
- Sprawdź
unstable_cache/ Data Cache → HIT = zwróć - Sprawdź Redis (współdzielony) → HIT = wynik trafi także do Data Cache
- MISS = pobierz z bazy → zapisz w Redis → wynik trafi do Data Cache
Rewalidacja cache w Next.js i Redis
Cache Next.js i wbudowane narzędzia rewalidacji
Redis: pełna kontrola i pełna odpowiedzialność
Hybrydowa rewalidacja cache
Jak uczciwie zmierzyć cache Next.js i Redis
Nie istnieje wiarygodna uniwersalna tabela typu „Next cache 1 ms, Redis 8 ms”. Lokalny LRU nie wykonuje sieciowego roundtripu, ale wynik całego requestu zależy również od serializacji, wielkości danych, cold startu i renderowania. Redis w tym samym regionie po trwałym połączeniu zachowa się inaczej niż REST do odległego regionu.
Mierz osobno:
- Hit, miss, stale hit i błąd cache
- p50, p95 i p99 czasu
GET,SEToraz całego requestu - Koszt odtworzenia danych ze źródła
- Rozmiar wartości po serializacji i transfer
- Cold oraz warm start, jeden i wiele regionów
- Zachowanie przy awarii Redisa oraz stampede
Hit ratio per rodzina kluczy, a nie tylko globalną średnią
Porównuj te same dane, tę samą wersję aplikacji i kontrolowany ruch. Cache o hit ratio 5% może dodawać koszt mimo świetnego czasu pojedynczego HIT. Z kolei wolniejszy współdzielony hit może chronić bazę skuteczniej niż szybki lokalny cache, do którego każdy proces trafia pierwszy raz.
Typowe błędy w cache Next.js i Redis
1. Brak jawnej polityki świeżości
Nie każdy klucz Redis musi posiadać TTL; liczniki, kolejki czy rekordy sesji mają własne wymagania i nie zawsze pełnią funkcję cache. Dla danych odtwarzalnych należy jednak zdefiniować świadomą politykę stale/revalidate/expire, określając maksymalną akceptowalną nieświeżość oraz sposób unieważniania. Zachowanie typu „nigdy nie wygasa” nie powinno być przypadkowym domyślnym ustawieniem.
2. Brak obsługi cache miss
3. Cache stampede
wygląda niewinnie: cache wygasa, a wszystkie requesty naraz lecą po świeże dane. Baza dostaje serię takich samych zapytań i nagle „cache” staje się generatorem ruchu.
TTL blokady powinien być dłuższy niż typowy czas odtworzenia danych lub bezpiecznie odnawiany. W przypadku wielu węzłów Redis i rygorystycznych wymagań warto zapoznać się z założeniami Redlock; blokada cache'owa nie zastępuje transakcji biznesowej. Często prostsze i skuteczniejsze okazują się strategie takie jak stale-while-revalidate, losowy jitter TTL oraz serwowanie ostatniej poprawnej wartości podczas odświeżania.
Cache Next.js czy Redis: co wybrać?
Mój domyślny wybór:
- Nowy projekt z Cache Components.
use cachei jawne profile świeżości. - Wiele instancji wymagających wspólnego cache renderowania.
use cache: remotez przetestowanym handlerem. - Liczniki, locki, sorted sets lub stan aplikacyjny. Jawny klient Redis, niezależnie od cache renderowania.
- Stary projekt bez Cache Components. Utrzymaj
unstable_cachedo zaplanowanej migracji, zamiast mieszać modele bez testów.
