# Opanuj cache'owanie i renderowanie w Next.js

> Strategie cache'owania i renderowania w Next.js 14 (wersja z 2025): SSG, ISR, SSR, CSR i PPR na przykładach z kodem. Wersja dla Next.js 16 jest w osobnym wpisie.

- URL: https://www.maciejbednarczyk.com/blog/mastering-caching-and-rendering-in-next-js
- Author: Maciej Bednarczyk
- Language: pl
- Published: 2025-05-02

> **Aktualizacja, listopad 2026:** ten wpis opisuje Next.js 14 i App Router z 2025. W Next.js 16 z Cache Components zmieniło się prawie wszystko, od `revalidate` po PPR. Nową wersję, z pomiarami na Next.js 16.4, znajdziesz tutaj: [Cache'owanie i renderowanie w Next.js w 2026: z wersji 14 na 16](https://www.maciejbednarczyk.com/pl/blog/nextjs-caching-rendering-2026).

## 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](https://assets.cmssy.io/6ae4795c65113d877fc8d877/5a62f871-96f5-46aa-9545-7bd665d73834-e96063f6ce0b.png)

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*?

<table><thead><tr><th>Ustawienie</th><th>Zachowanie</th></tr></thead><tbody><tr><td>dynamicParams: true</td><td>Jeśli strona nie została zbudowana wcześniej, zostanie wyrenderowana dynamicznie przy żądaniu</td></tr><tr><td>dynamicParams: false</td><td>Jeśli strona nie została wygenerowana w czasie builda, zwróci 404</td></tr></tbody></table>

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

```
RootLayout.tsx
├── Header.tsx → fetch(..., { revalidate: 60 }) ❗
└── Page.tsx   → revalidate = 604800 (7 days)
```

![Diagram ISR](https://assets.cmssy.io/6ae4795c65113d877fc8d877/4d2aff37-ceca-45ae-a149-9166f8e6a4a0-97e2662bc9bd.png)

#### 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.

**Status w Next.js 16:** dawnych eksperymentalnych flag PPR już nie ma. PPR działa teraz w ramach Cache Components: włącz `cacheComponents: true` w `next.config`, a dane będą domyślnie dynamiczne, dopóki nie oznaczysz ich przez `"use cache"`. Po włączeniu tej opcji `"use cache"` i `cacheLife` zastępują opcje segmentu trasy takie jak `dynamic` i `revalidate`.

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
-   **"use cache"** - do oznaczenia tego, co trafia do statycznej powłoki
-   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
// next.config.ts - enable PPR with: cacheComponents: true
// app/ppr-example/page.tsx

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

export default async function Page() {
  "use cache"; // part of the static shell
  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>
  );
}
```

> **Poprawka (listopad 2026):** poniższy przykład to nie jest PPR. Komponent kliencki z `useEffect` nie zawiesza renderu, więc trafia do statycznej powłoki i pobiera dane dopiero po hydracji. To zwykły CSR w statycznej powłoce. Dynamiczna część PPR to komponent serwerowy w `<Suspense>`, np. taki, który czyta `cookies()`. Poprawny przykład jest w [nowym wpisie](https://www.maciejbednarczyk.com/pl/blog/nextjs-caching-rendering-2026).

**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](https://assets.cmssy.io/6ae4795c65113d877fc8d877/3cc0670f-4e13-4148-9f18-d69bc4f26650-18fda663229c.png)

#### 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 Server Components) 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
import "server-only";

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**

<table><thead><tr><th>Funkcja</th><th>Zastosowanie</th><th>Poziom</th></tr></thead><tbody><tr><td>revalidateTag("...", "max")</td><td>Unieważnia wszystkie fetche z tym tagiem</td><td>Poziom fetcha</td></tr><tr><td>revalidatePath("/...")</td><td>Unieważnia cache strony dla konkretnej ścieżki</td><td>Poziom strony</td></tr></tbody></table>

#### 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", "max");
  return new Response("Tag revalidated");
}
```

💡 Oznaczy to **wszystkie dane w cache'u pobrane z tym tagiem** jako nieaktualne: przy kolejnej wizycie użytkownik dostanie starą wersję, a świeże dane załadują się w tle - świetny sposób na aktualizację fragmentów strony bez przebudowy. W Next.js 16 wariant z jednym argumentem jest przestarzały (zalecany profil to `"max"`). Do natychmiastowego wygaszenia użyj `updateTag()` w Server Actions albo `{ expire: 0 }` w webhookach.

#### 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 { updateTag } from "next/cache";
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";
          updateTag("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", "max");
    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

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

* * *

## Pełna tabela porównawcza

<table><thead><tr><th>Strategia</th><th>Świeżość danych</th><th>Wydajność</th><th>Przyjazne SEO</th><th>Kontrola cache'a</th></tr></thead><tbody><tr><td>SSG</td><td>❌ Tylko statyczne</td><td>⚡⚡⚡ Bardzo szybko</td><td>✅ Tak</td><td>Budowane raz przy wdrożeniu</td></tr><tr><td>ISR</td><td>🟡 Okresowo lub na żądanie</td><td>⚡⚡ Szybko</td><td>✅ Tak</td><td>revalidate lub tagi</td></tr><tr><td>SSR</td><td>✅ Zawsze świeże</td><td>⚠️ Wolniej</td><td>✅ Tak</td><td>Bez cache'a (na żądanie)</td></tr><tr><td>PPR</td><td>🔀 Mieszane</td><td>⚡ Szybko + elastycznie</td><td>✅ Tak</td><td>Statyka + Suspense</td></tr><tr><td>CSR</td><td>✅ Zawsze świeże</td><td>🐢 Wolne pierwsze ładowanie</td><td>❌ Nie</td><td>Tylko przeglądarka</td></tr><tr><td>react.cache()</td><td>♻️ Ponowne użycie w ramach żądania</td><td>⚡ Szybsze rendery</td><td>✅ Tak</td><td>Deduplikacja na poziomie kodu</td></tr></tbody></table>

* * *

## 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: **Cache Components + 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ę](https://assets.cmssy.io/6ae4795c65113d877fc8d877/46033998-c786-4503-85b7-166b2a951a52-495ef05e69cd.png)

## 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ć**.