Opanuj cache'owanie i renderowanie w Next.js

Dowiedz się, jak strategie cache'owania i renderowania w Next.js 14 wpływają na wydajność i skalowalność - z prawdziwymi przykładami i kodem dla SSG, ISR, SSR, CSR i PPR.

Wprowadzenie

Szybkie, skalowalne i niezawodne aplikacje webowe to już nie luksus - to standard. Użytkownicy oczekują natychmiastowego ładowania, treści w czasie rzeczywistym i płynnej interakcji. Jako deweloperzy ciągle balansujemy między wydajnością, świeżością danych i kosztami serwera.

I tu Next.js pokazuje, co potrafi.

Next.js to nie tylko framework do Reacta - to zaawansowana platforma, która daje wiele sposobów kontrolowania tego, jak dane są pobierane, renderowane i cache'owane. Wraz z pojawieniem się App Routera cache'owanie i renderowanie mocno się zmieniły.

Tylko że pojawia się problem:

❓ Wyrenderować tę stronę statycznie z wyprzedzeniem, czy dynamicznie przy każdym żądaniu?

❓ Jak cache'ować kosztowne zapytania, żeby dane się nie zestarzały?

❓ Czym jest ISR? To tylko modne hasło?

Jeśli te pytania brzmią znajomo, jesteś we właściwym miejscu.

Czym jest cache'owanie w Next.js?

Zmagania z cache'owaniem
Zmagania z cache'owaniem

Cache'owanie to tymczasowe przechowywanie danych lub treści, żeby można było użyć ich ponownie, zamiast za każdym razem liczyć je lub pobierać od nowa. W web devie cache'owanie jest kluczowe dla szybkości, skalowalności i odporności - a w Next.js jest głęboko wbudowane w sposób, w jaki aplikacja jest budowana i serwowana.

Pomyśl o cache'owaniu jak o sprytnej pamięci:

Skoro raz już coś zrobiłeś (pobrałeś dane albo wyrenderowałeś HTML), to po co robić to znowu?

Zamiast pobierać świeże dane przy każdym żądaniu albo renderować każdą stronę od zera, możesz zapisać wynik (HTML, JSON, odpowiedź API, wynik funkcji) i użyć go ponownie.

Dzięki temu unikasz:

  • Powtarzania kosztownych wywołań bazy danych i API
  • Renderowania w kółko tego samego
  • Opóźnień odczuwalnych dla użytkownika

Po co nam cache'owanie?

Cache'owanie zapewnia:

  • ⚡️ Szybsze odpowiedzi (bez zbędnego pobierania i renderowania)
  • 📈 Mniejsze obciążenie serwera (te same odpowiedzi dla wielu żądań)
  • 🔄 Rozsądną świeżość danych (kontrolowane aktualizacje i rewalidacja)
  • 🚀 Lepszą skalowalność (dane z cache'a obsługują tysiące użytkowników)

Wzorce renderowania

Renderowanie sprowadza się do jednego prostego pytania:

"Kiedy i gdzie wygenerować HTML dla tej strony?"

Next.js daje Ci pełną kontrolę nad tą odpowiedzią - strona po stronie, komponent po komponencie. Każdy wzorzec renderowania w Next.js to kompromis między szybkością, świeżością danych i skalowalnością, a wiedza, kiedy którego użyć, robi ogromną różnicę przy budowaniu szybkich i łatwych w utrzymaniu aplikacji.

Static Site Generation (SSG)

  • HTML jest generowany raz, w czasie builda.
  • Serwowany natychmiast z systemu plików lub cache'a na edge'u.
  • Błyskawiczne działanie, idealne dla treści, które rzadko się zmieniają.

SSG - jak to działa?

Gdy uruchomisz:

bash
next build

Next.js:

  1. Wywołuje Twoje funkcje serwerowe (np. pobranie użytkowników)
  2. Generuje statyczny HTML + JSON
  3. Zapisuje je na dysku lub w cache'u na edge'u

SSG - przykład

tsx
// app/ssg-example/page.tsx
export const dynamic = "force-static"; // ensure it's cached at build time

export default async function Page() {
  const users = await getServerUserList(); // this runs at build!
  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(u => <li key={u.id}>{u.name}</li>)}
      </ul>
    </div>
  );
}

🟡 Ważne: jeśli getServerUserList() pobiera dane z działającego API, pamiętaj, że nie zaktualizują się one, dopóki nie przebudujesz aplikacji.

Wszyscy użytkownicy dostają tę samą wersję strony, a ładuje się ona bardzo szybko.

SSG - o czym pamiętać

  • Jeśli Twoje dane muszą się zmieniać, SSG nie pokaże aktualizacji bez ponownego wdrożenia.
  • Dobre dla treści tylko do odczytu.
  • Użyj force-static, żeby jawnie włączyć SSG w App Routerze.

Server-Side Rendering (SSR)

  • HTML jest generowany przy każdym żądaniu, na serwerze.
  • Dane na stronie są zawsze świeże - ale jest wolniej i drożej.
  • Najlepsze dla treści mocno dynamicznych lub zależnych od użytkownika.

SSR - jak to działa?

Gdy użytkownik wchodzi na stronę:

  1. Next.js uruchamia Twoją funkcję serwerową (np. pobranie użytkowników)
  2. Generuje HTML w locie
  3. Wysyła odpowiedź do przeglądarki

Dzieje się to za każdym razem, gdy ktoś zażąda strony.

SSR - przykład

tsx
// app/ssr-example/page.tsx
export const dynamic = "force-dynamic"; // always render on the server

export default async function Page() {
  const users = await getServerUserList(); // runs on every request
  const now = new Date().toLocaleTimeString();

  return (
    <div>
      <h1>Live Users (SSR)</h1>
      <p>Generated at: {now}</p>
      <ul>
        {users.map(u => <li key={u.id}>{u.name}</li>)}
      </ul>
    </div>
  );
}

Przy każdym odświeżeniu strony dane są pobierane od nowa, a HTML generowany ponownie.

SSR - o czym pamiętać

  • SSR może zwalniać przy dużym ruchu, bo każde żądanie wymaga pełnego renderu.
  • Świetne dla treści dynamicznych, ale używaj go tylko wtedy, gdy to konieczne.
  • Jeśli dane da się cache'ować albo nie zależą od użytkownika, rozważ ISR lub SSG.

Incremental Static Regeneration (ISR)

  • HTML jest renderowany z wyprzedzeniem jak w SSG, ale można go zaktualizować po wdrożeniu.
  • Strony są cache'owane i rewalidowane okresowo lub na żądanie.
  • Idealny balans między szybkością a świeżością danych.

ISR - jak działa ISR?

  1. W czasie builda Next.js renderuje wybrane strony do statycznego HTML-a.
  2. Strony te są serwowane natychmiast z cache'a.
  3. Po pewnym czasie (np. 60 sekundach), gdy przyjdzie nowe żądanie:
    • Next.js wygeneruje stronę ponownie w tle
    • Nowa wersja zastąpi starą w cache'u
    • Kolejni użytkownicy dostaną świeżą wersję

Dzięki temu masz:

  • Szybkie pierwsze ładowanie ✅
  • Automatycznie aktualizowaną treść ✅
  • Małe obciążenie serwera ✅

ISR - przykład bez generateStaticParams

tsx
// app/isr-no-params/page.tsx
export const revalidate = 60; // refresh this page every 60 seconds

export default async function Page() {
  const posts = await getPostList();

  return (
    <div>
      <h1>Latest Blog Posts</h1>
      <ul>
        {posts.map(p => <li key={p.id}>{p.title}</li>)}
      </ul>
    </div>
  );
}
  • Pierwsze żądanie: strona jest generowana i cache'owana.
  • Po 60 s: kolejne żądanie wygeneruje stronę ponownie.

ISR - przykład z generateStaticParams

tsx
export const revalidate = 60;
export const dynamicParams = true;

export async function generateStaticParams() {
  const posts = await getPostList();
  return posts.slice(0, 5).map(post => ({ id: post.id }));
}
  • Pierwsze 5 stron powstaje w czasie builda.
  • Pozostałe są tworzone na żądanie, gdy użytkownik na nie wejdzie.
  • Wszystkie strony są cache'owane i automatycznie odświeżane po 60 s.

ISR - co oznacza dynamicParams?

UstawienieZachowanie
dynamicParams: trueJeśli strona nie została zbudowana wcześniej, zostanie wyrenderowana dynamicznie przy żądaniu
dynamicParams: falseJeśli strona nie została wygenerowana w czasie builda, zwróci 404

Daje Ci to kontrolę nad tym, co jest statyczne, a co dynamiczne.

ISR - jeden mały revalidate może zepsuć cache'owanie całej strony ⚠️

Załóżmy, że budujesz serwis z dokumentacją i chcesz, żeby każda strona rewalidowała się tylko raz w tygodniu:

tsx
export const revalidate = 60 * 60 * 24 * 7; // 7 days

Wszystko wygląda dobrze. Strony są statyczne, szybkie i tanie w utrzymaniu.

Później dodajesz do layoutu pasek statusu systemu, który co 60 sekund pobiera dane z API:

tsx
await fetch("https://status.example.com/api", {
  next: { revalidate: 60 }, // 1 minute
});

Teraz każda strona dokumentacji rewaliduje się co minutę - a nie co 7 dni.

🔥 Next.js bierze najkrótszą wartość revalidate znalezioną gdziekolwiek w drzewie renderowania

Oznacza to, że layouty i głęboko zagnieżdżone komponenty mogą nieświadomie skrócić czas życia cache'a całej strony.

💡 Jak tego uniknąć

  • Nie umieszczaj fetchy z krótkim revalidate w layoutach ani komponentach głównych
  • Jeśli potrzebujesz danych na żywo, takich jak status, rozważ:
    • Renderowanie po stronie klienta (CSR)
    • Suspense + dynamiczną granicę
    • Przeniesienie fetcha do useEffect, jeśli to czysto wizualny element

🧪 Jak to wykryć

Next.js Cię przed tym nie ostrzeże.

Żeby to znaleźć:

  • Otwórz w przeglądarce zakładkę Network
  • Sprawdź nagłówki odpowiedzi strony:
    • age - jak długo strona jest już w cache'u
    • cache-control - jak długo może w nim pozostać
  • Jeśli age jest zawsze niskie → rewalidujesz częściej, niż Ci się wydaje
plaintext
RootLayout.tsx
├── Header.tsx → fetch(..., { revalidate: 60 }) ❗
└── Page.tsx   → revalidate = 604800 (7 days)
Diagram ISR
Diagram ISR

ISR - o czym pamiętać

  • Unikaj łączenia komponentów o bardzo różnych czasach rewalidacji
  • Nie umieszczaj fetchy na żywo w globalnych layoutach
  • Do małych dynamicznych fragmentów używaj renderowania po stronie klienta (useEffect) lub granic Suspense
  • Regularnie sprawdzaj zachowanie cache'a (np. w observability na Vercelu)

Partial Pre-Rendering (PPR)

  • Część strony jest statyczna, a część dynamiczna.
  • Korzysta z React Server Components + Suspense.
  • Świetne dla layoutów, które mają ładować się natychmiast, podczas gdy treść dynamiczna dopływa strumieniowo.

Pozwala wyrenderować z wyprzedzeniem tylko część strony, a pozostałe części są pobierane i renderowane dynamicznie na serwerze lub kliencie.

Cel?

Połączyć statyczną szybkość z dynamiczną elastycznością - na tej samej stronie.

PPR - jak to działa?

PPR korzysta z:

  • React Server Components (RSC) - do treści statycznych i dynamicznych renderowanych na serwerze
  • <Suspense> - do wydzielenia części strony jako dynamicznych jednostek
  • Opcjonalnie komponentów klienckich - do danych interaktywnych lub w czasie rzeczywistym

Next.js streamuje HTML:

  1. Części statyczne pojawiają się natychmiast
  2. Części dynamiczne dopływają, gdy są gotowe
  3. Wszystko to dzieje się w ramach jednego żądania strony

PPR - przykład

Layout strony (renderowanie mieszane)

tsx
// app/ppr-example/page.tsx

import { getPostList } from "@/services/server";
import { Suspense } from "react";
import DynamicUserBox from "./_components/dynamic-user-box";

export const revalidate = 0; // this page is dynamic

export default async function Page() {
  const posts = await getPostList();
  const generatedAt = new Date().toLocaleString();

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold">📚 Blog + Live Data (PPR)</h1>

      <p className="text-gray-500">Static part generated at: {generatedAt}</p>

      <section className="mt-6">
        <h2 className="text-xl font-semibold">📄 Static Blog List (SSG)</h2>
        <ul className="list-disc pl-5">
          {posts.map((p) => (
            <li key={p.id}>{p.title}</li>
          ))}
        </ul>
      </section>

      <hr className="my-6" />

      <section>
        <h2 className="text-xl font-semibold">⚡ Live User Data</h2>
        <Suspense fallback={<p>Loading users…</p>}>
          <DynamicUserBox />
        </Suspense>
      </section>
    </main>
  );
}

Dynamiczny komponent kliencki

tsx
// app/ppr-example/_components/DynamicUserBox.tsx

"use client";

import { useEffect, useState } from "react";
import { getClientUserList } from "@/services/client";
import { User } from "@/types";

export default function DynamicUserBox() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getClientUserList().then((data) => {
      setUsers(data);
      setLoading(false);
    });
  }, []);

  if (loading) return <p className="text-blue-500">Fetching users…</p>;

  return (
    <div className="p-4 bg-gray-100 rounded">
      <h3 className="font-medium mb-2">👥 Active Users</h3>
      <ul className="list-disc pl-5">
        {users.map((u) => (
          <li key={u.id}>{u.name}</li>
        ))}
      </ul>
    </div>
  );
}
Mapa PPR
Mapa PPR

PPR - o czym pamiętać

  • Używaj PPR, gdy chcesz, żeby layout i główna treść ładowały się szybko
  • Owijaj sekcje dynamiczne w <Suspense>, żeby streamować je osobno
  • Używaj komponentów "use client" do danych, które zmieniają się dla każdego użytkownika lub w czasie rzeczywistym
  • Trzymaj części dynamiczne małe - nie opóźniaj całej strony

Client-Side Rendering (CSR)

  • HTML jest w większości statyczny; dane są pobierane w przeglądarce.
  • W pełni dynamiczne, świetne do interakcji.
  • Ale wolniejsze pierwsze ładowanie i kiepskie SEO.

CSR - jak to działa?

  1. Next.js wysyła do przeglądarki podstawową powłokę HTML.
  2. React ładuje się i hydratuje komponenty.
  3. Potem Twoje komponenty pobierają dane po stronie klienta, za pomocą fetch() lub useEffect().

Oznacza to, że:

  • Nic nie jest pobierane ani renderowane na serwerze
  • Masz maksymalną elastyczność, ale strona może początkowo ładować się wolniej

CSR - przykład

tsx
// app/csr-example/page.tsx

import { UserClientList } from "./_components/user-client-list";

export default function Page() {
  const now = new Date().toLocaleString();

  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold mb-4">👤 Client-Side Rendering</h1>
      <p className="text-gray-500 mb-2">Page loaded at: {now}</p>
      <UserClientList />
    </div>
  );
}
tsx
// app/csr-example/_components/user-client-list.tsx

"use client";

import { useEffect, useState } from "react";
import { getClientUserList } from "@/services/client";
import { User } from "@/types";

export const UserClientList = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getClientUserList().then((data) => {
      setUsers(data);
      setLoading(false);
    });
  }, []);

  if (loading) return <p>Loading users...</p>;

  return (
    <ul className="list-disc pl-5">
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
};

CSR - o czym pamiętać

  • Używaj CSR dla stron nieindeksowanych i zależnych od użytkownika
  • Wyraźnie pokazuj stany ładowania
  • Jeśli to możliwe, pobieraj krytyczne dane z wyprzedzeniem
  • Dbaj o lekkość komponentów, żeby skrócić czas hydratacji

Cache'owanie na poziomie funkcji w Next.js ⚛️ react.cache()

Next.js (dzięki React 18) pozwala cache'ować wynik dowolnej funkcji asynchronicznej za pomocą react.cache().

To cache'owanie na poziomie funkcji - bardzo przydatne, gdy chcesz uniknąć zduplikowanych fetchy lub kosztownych obliczeń podczas renderowania na serwerze.

✅ Kiedy tego używać?

Użyj react.cache(), gdy:

  • Chcesz deduplikować wywołania fetch w ramach jednego żądania
  • Wywołujesz tę samą funkcję wiele razy w jednym drzewie renderowania
  • Chcesz mieć precyzyjną kontrolę bez polegania na rewalidacji całej strony

Najlepiej działa w:

  • React Server Components
  • Funkcjach wyłącznie serwerowych
  • Sytuacjach wymagających wewnętrznego cache'owania

Jak to działa?

Przy pierwszym wywołaniu z danym argumentem funkcja się wykonuje i zwraca wynik.

Kolejne wywołania (w ramach tego samego żądania) zwracają wartość z cache'a bez ponownego uruchamiania logiki.

Oszczędza to czas, pieniądze i transfer - zwłaszcza przy kosztownych zapytaniach do bazy lub API.

Przykład

typescript
// utils/getServerUser.ts
"use server";

import { cache } from "react";

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

// This function will cache its result during a single request
export const getServerCurrentUser = cache(async () => {
  console.log("🧠 Fetching current user...");
  const res = await fetch(`${baseUrl}/api/user`);
  if (!res.ok) throw new Error("Failed to fetch user");
  return res.json();
});
tsx
// app/cache-example/page.tsx

import { getServerCurrentUser } from "@/utils/getServerUser";
import { UserInfo } from "./_components/user-info";

export default async function Page() {
  // These will both return the same cached result (no second fetch)
  const user1 = await getServerCurrentUser();
  const user2 = await getServerCurrentUser();

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">🧠 Function-Level Caching</h1>
      <p className="text-gray-500 mb-4">
        Fetching same user twice – only the first call actually runs.
      </p>
      <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
        <UserInfo title="Left Column" user={user1} />
        <UserInfo title="Right Column" user={user2} />
      </div>
    </div>
  );
}

⚠️ Ważne uwagi

  • react.cache() cache'uje tylko w ramach jednego żądania - nie jest globalny ani współdzielony między żądaniami.
  • Do trwałego cache'owania (np. między żądaniami) użyj next: { revalidate } albo cache'a edge/CDN.
  • Musisz wywoływać go w kontekście wyłącznie serwerowym (nie w komponentach klienckich).
  • Dobrze współpracuje z React Server Components, zwłaszcza w App Routerze.

Dobre praktyki

  • Owijaj często używane funkcje serwerowe w cache(...)
  • Łącz z revalidateTag() lub revalidatePath(), żeby sprytnie unieważniać cache
  • Nie nadużywaj go przy funkcjach o dużej zmienności lub z danymi zależnymi od użytkownika

Ręczna rewalidacja w Next.js

O ile revalidate pozwala określić, kiedy strona lub fetch mają się odświeżyć automatycznie, o tyle ręczna rewalidacja daje pełną kontrolę nad tym, kiedy treść się aktualizuje - natychmiast, na żądanie.

Idealnie nadaje się do:

  • Paneli administracyjnych ("Odśwież teraz wpisy")
  • Integracji z CMS-em ("Zaktualizuj tę ścieżkę")
  • Unieważniania cache'a po wysłaniu formularza
  • Oszczędzania na kosztach dzięki uniknięciu zbędnej rewalidacji

Dwa kluczowe API do rewalidacji na żądanie

FunkcjaZastosowaniePoziom
revalidateTag("...")Unieważnia wszystkie fetche z tym tagiemPoziom fetcha
revalidatePath("/...")Unieważnia cache strony dla konkretnej ścieżkiPoziom strony

revalidateTag() - przykład

Żądania fetch możesz otagować tak:

typescript
await fetch("https://api.example.com/posts", {
  next: { tags: ["posts"] },
});

Następnie w server action lub route:

typescript
import { revalidateTag } from "next/cache";

export async function POST() {
  revalidateTag("posts");
  return new Response("Tag revalidated");
}

💡 Unieważni to wszystkie dane w cache'u pobrane z tym tagiem - świetny sposób na aktualizację fragmentów strony bez przebudowy.

revalidatePath() - przykład

typescript
import { revalidatePath } from "next/cache";

revalidatePath("/blog");
revalidatePath(`/product/${id}`);

Przydaje się, gdy chcesz wygenerować ponownie całą stronę, a nie tylko konkretne dane.

Przykład z życia (przycisk odświeżania w panelu admina)

tsx
// app/revalidate-on-demand/page.tsx

import { cache } from "react";
import { User } from "@/types";

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

const fetchUsers = cache(async () => {
  const res = await fetch(`${baseUrl}/api/users`, {
    next: { tags: ["users"] },
  });
  return res.json();
});

export default async function Page() {
  const users: User[] = await fetchUsers();

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold mb-4">🔁 Revalidate Tag Example</h1>

      <form
        action={async () => {
          "use server";
          revalidateTag("users");
        }}
      >
        <button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">
          🔄 Refresh Users
        </button>
      </form>

      <ul className="mt-6 pl-5 list-disc">
        {users.map((u) => (
          <li key={u.id}>{u.name}</li>
        ))}
      </ul>
    </main>
  );
}

Wersja z route API

Czasem lepiej rewalidować z akcji zewnętrznej (np. webhooka, CMS-a lub przycisku).

typescript
// app/api/revalidate-users/route.ts

import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

export async function POST() {
  try {
    revalidateTag("users");
    return NextResponse.json({ revalidated: true });
  } catch (e) {
    return NextResponse.json({ error: "Failed" }, { status: 500 });
  }
}

Potem wywołaj go z klienta albo webhooka CMS-a!

Dobre praktyki

  • Używaj revalidateTag() do odświeżania danych, a revalidatePath() do aktualizacji całych stron
  • Jeśli to możliwe, wybieraj server actions - są proste i szybkie
  • Łącz z cache(), żeby kontrolować kosztowne fetche
  • Monitoruj przez DevTools lub nagłówki Vercela, żeby potwierdzić unieważnienie cache'a

Jak wybrać strategię renderowania i cache'owania

Przypadek użyciaZalecana strategia
💾 Treść statyczna, rzadko się zmieniaStatic Site Generation (SSG)
📰 Treść zmienia się od czasu do czasu, ale musi być szybkoIncremental Static Regeneration (ISR)
📊 Dane na żywo lub zależne od użytkownika (np. dashboard)Server-Side Rendering (SSR)
🧩 Część strony jest dynamiczna (np. sidebar)Partial Pre-Rendering (PPR)
🙋♂️ Widok dla zalogowanego użytkownika, SEO niepotrzebneClient-Side Rendering (CSR)
⚡ Unikanie duplikatów fetchy w ramach jednego żądaniaCache'owanie na poziomie funkcji za pomocą react.cache()
🔄 Ręczne wyzwalanie aktualizacji (np. z CMS-a)revalidateTag() / revalidatePath()

Pełna tabela porównawcza

StrategiaŚwieżość danychWydajnośćPrzyjazne SEOKontrola cache'a
SSG❌ Tylko statyczne⚡⚡⚡ Bardzo szybko✅ TakBudowane raz przy wdrożeniu
ISR🟡 Okresowo lub na żądanie⚡⚡ Szybko✅ Takrevalidate lub tagi
SSR✅ Zawsze świeże⚠️ Wolniej✅ TakBez cache'a (na żądanie)
PPR🔀 Mieszane⚡ Szybko + elastycznie✅ TakStatyka + Suspense
CSR✅ Zawsze świeże🐢 Wolne pierwsze ładowanie❌ NieTylko przeglądarka
react.cache()♻️ Ponowne użycie w ramach żądania⚡ Szybsze rendery✅ TakDeduplikacja na poziomie kodu

Szybkie zasady do zapamiętania

  • Domyślnie zawsze wybieraj statykę (SSG lub ISR), chyba że potrzebujesz treści w czasie rzeczywistym
  • Używaj revalidateTag(), żeby aktualizować selektywnie, bez nadmiarowego pobierania
  • Nie mieszaj szybko rewalidowanych fetchy w globalnych layoutach - może to po cichu zepsuć cache'owanie
  • Łącz strategie: SSG + Suspense = PPR, SSR + react.cache() = wydajne SSR
  • Obserwuj nagłówki odpowiedzi (age, x-vercel-cache), żeby debugować cache'owanie na produkcji

NEXT.js - jak wybrać strategię renderowania i cache'owania

Infografika: jak wybrać strategię
Infografika: jak wybrać strategię

Na koniec

Używaj cache'owania nie tylko po to, żeby było szybciej - używaj go, żeby budować mądrzejsze, tańsze i bardziej skalowalne aplikacje. Next.js daje Ci narzędzia - to Ty decydujesz, jak je połączyć.