C
Next.js/Rendering/Lesson 05

Data Fetching — 4 fetch Cache Policies + revalidate

35 min·theory
This chapter
2/5
TypeScript

Data Fetching — 4 fetch Cache Policies + revalidate

💡 Why Does This Matter? — Cache Policy Determines Page Speed

🎯 The `getStaticProps` (SSG), `getServerSideProps` (SSR), and ISR options from the Pages Router have all been unified into **a single fetch option**.
💼 Because fetch is automatically deduped and cached, the old concern that 'calling the same data from multiple components is inefficient' is no longer an issue.
By combining `revalidate` (time-based) and `tags` (manual invalidation), you can precisely define the freshness policy for each page.
🔗 Without understanding this, every page defaults to SSG caching — causing issues like 'I published a new post but it is not showing up' — or conversely, overusing `no-store` increases server load.
🏢 실무에서는
Since codemaster40's learning content rarely changes, SSG (`force-cache`) is appropriate. In contrast, user progress and bookmarks vary per user and change frequently, so `no-store` is the right choice. For content like boards and comments, a `revalidate` of 1–5 minutes strikes a good balance. The ability to make this decision at the level of each individual fetch call is a key strength of the App Router.

4 fetch Cache Policies + How to Revalidate

1. Default — force-cache (SSG)

tsx
const res = await fetch('https://api/posts');
// Fetched once at build time → result is permanently cached
// Revisiting the page returns the same result (until the next deploy)

Fast, but changes to the data won't be reflected. Best suited for data that rarely changes.

2. no-store (SSR)

tsx
const res = await fetch('https://api/posts', {
  cache: 'no-store',
});
// Fresh fetch on every request — equivalent to getServerSideProps in the Pages Router

Always up to date. Use for per-user data, live prices, or inventory where freshness on every request matters.

3. revalidate (ISR — Incremental Static Regeneration)

tsx
const res = await fetch('https://api/posts', {
  next: { revalidate: 60 },
});
// Cached for 60 seconds; next request after expiry triggers background regeneration

Combines the speed of caching with freshness. Ideal for blogs, docs, and content that updates occasionally.

4. tags + revalidateTag (On-Demand Invalidation)

tsx
// Fetch data and attach a tag
const res = await fetch('https://api/posts', {
  next: { tags: ['posts'] },
});

// Invalidate the tag from another location (e.g., a Server Action)
import { revalidateTag } from 'next/cache';
async function createPost(formData: FormData) {
  'use server';
  await db.post.create({ ... });
  revalidateTag('posts'); // Invalidates all fetch caches tagged 'posts'
}

Perfect for on-demand updates (e.g., immediately after publishing a post). ISR + tags is the standard production combination.

5. Path-Level Invalidation — revalidatePath

tsx
import { revalidatePath } from 'next/cache';
revalidatePath('/blog');     // Invalidates all cache for the /blog page
revalidatePath('/blog/[slug]', 'page'); // Invalidates all dynamic paths

6. Summary — When to Use What

Data CharacteristicsRecommended Policy
Rarely changes (learning content, landing pages)force-cache (default)
Acceptable to update every few minutes (blog, news)revalidate: 60~300
Per-user or real-timeno-store
Immediate refresh after a user actiontags + revalidateTag
💻 🅰️ Pages Router — Separate getStaticProps / getServerSideProps
// ❌ Pages Router — Different functions for each mode

// 📁 pages/blog/index.tsx (SSG)
import type { GetStaticProps } from 'next';

interface Post { id: number; title: string; }

export const getStaticProps: GetStaticProps<{ posts: Post[] }> = async () => {
  const posts = await fetch('https://api/posts').then(r => r.json());
  return {
    props: { posts },
    revalidate: 60, // ISR — 60 seconds
  };
};

export default function Blog({ posts }: { posts: Post[] }) {
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

// 📁 pages/dashboard.tsx (SSR — every request)
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps<{ user: User }> = async (ctx) => {
  const user = await fetchUser(ctx.req.cookies.session);
  return { props: { user } };
};

export default function Dashboard({ user }: { user: User }) {
  return <h1>Hello, {user.name}</h1>;
}

// Disadvantages:
// - Fetching mode determined per page (e.g., cannot have only part of a page as ISR)
// - Data serialized as props → inefficient for large objects
💻 🅱️ App Router — Unified via a Single fetch Option
// ✅ App Router — Cache policy determined by fetch options

interface Post { id: number; title: string; }
interface User { id: number; name: string; }

// 📁 app/blog/page.tsx — ISR 60 seconds
export default async function Blog() {
  const posts: Post[] = await fetch('https://api/posts', {
    next: { revalidate: 60, tags: ['posts'] },
  }).then(r => r.json());

  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

// 📁 app/dashboard/page.tsx — SSR (every request)
import { cookies } from 'next/headers';

export default async function Dashboard() {
  const session = cookies().get('session')?.value;
  const user: User = await fetch(`https://api/users/me`, {
    cache: 'no-store', // every request
    headers: { cookie: `session=${session}` },
  }).then(r => r.json());

  return <h1>Hello, {user.name}</h1>;
}

// 📁 app/mixed/page.tsx — Different policies within a single page
export default async function Mixed() {
  // Static content — permanent cache
  const intro = await fetch('https://api/intro').then(r => r.json());

  // Infrequently updated — 5 minutes
  const posts = await fetch('https://api/posts', {
    next: { revalidate: 300 },
  }).then(r => r.json());

  // User data — every time
  const me = await fetch('https://api/me', {
    cache: 'no-store',
  }).then(r => r.json());

  return <Layout intro={intro} posts={posts} me={me} />;
}

// 📁 app/posts/actions.ts — Invalidate cache after writing a post
'use server';
import { revalidateTag } from 'next/cache';

export async function createPost(formData: FormData) {
  await db.post.create({
    data: { title: formData.get('title') as string },
  });
  revalidateTag('posts'); // Invalidate all fetch caches with tags: ['posts']
}

💡 💡 Next.js Data Fetching: 5 Practical Tips

1. force-cache is the default — data is cached unless you say otherwise
Fetches at build time and caches permanently. The number-one reason you don't see updated data.

2. Heuristics for choosing a revalidate interval

  • Content changes less than once per minute → 60
  • Changes a few times per hour → 300
  • Rarely changes → 3600 or more, or forever (force-cache + tags)

3. Combining tags with revalidateTag is the most powerful approach
A single revalidateTag('posts') call in a create/update/delete action invalidates all related caches.

4. Same URL is auto-deduplicated — no need to worry about call count
Even if multiple components call fetch('/api/me') within the same render, only one actual network request is made.

5. Calling cookies() or headers() forces the route to be dynamic

tsx
import { cookies } from 'next/headers';
const c = cookies(); // This line overrides force-cache and makes every request SSR

The page switches to dynamic mode regardless of the fetch cache setting.

⚡ Try It Yourself — Cache Policy Simulator

Simulate how the cache policy of each fetch call affects the freshness of the page.
✏️ JS 코드
📟 Console output
▶ Press the Run button
⚠️ Runs in a browser sandbox — only console.log() is supported; alert/fetch are not.

Check Your Understanding

What is the default caching behavior of `fetch(url)` (no options) in the Next.js App Router?
💡 The default value of fetch in Next.js 13/14 is `force-cache`. Calling fetch without any options causes it to be invoked once at build time, with the result cached permanently (until the next deployment). To update data, you must explicitly specify one of `revalidate`, `tags`, or `no-store`. ⚠️ There is an RFC indicating that the default changes to `no-store` starting from Next.js 15, so be sure to check the version you are using.
Data Fetching — 4 fetch Cache Policies - Next.js