aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Development

Next.js Data Fetching: fetch and Caching Strategies

Next.js data fetching is one of the first things that confuses developers after they move to the App Router. That's because what shapes your page's behaviour is no longer just where and how you fetch the data, but when and for how long that data is cached. The same fetch call can turn a page fully static, regenerated on every request, or refreshed at fixed intervals, depending on a couple of options you pass. In this article I'll explain the differences between static rendering, SSR and ISR with concrete examples.

The basics of fetching in Server Components

In the App Router, components are Server Components by default. That means you can make a component async and call await fetch(...) right inside it. No separate useEffect, state management or loading flag is needed; the data is prepared on the server and embedded into the HTML sent to the browser.

// app/products/page.tsx
export default async function ProductsPage() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

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

The crucial point: whether this code runs statically or dynamically depends on the cache and next options you pass to fetch.

Static rendering: build once, serve forever

Static rendering produces the page once at build time and serves the resulting HTML to users over and over. It's ideal for content that rarely changes — blog posts, documentation, product descriptions — because every request is answered as fast as a CDN file.

An important detail: with Next.js 15 the default behaviour of fetch changed. Requests are now not cached by default (in Next.js 14 the default was cached). If you want static, cached behaviour, you have to opt in explicitly:

// Cache the result permanently -> static
const res = await fetch('https://api.example.com/products', {
  cache: 'force-cache',
});

For dynamic-segment routes (e.g. app/blog/[slug]/page.tsx) you declare which pages to pre-render with generateStaticParams:

export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then((r) => r.json());
  return posts.map((p) => ({ slug: p.slug }));
}

SSR: fresh data on every request

Server-Side Rendering (SSR) produces the page on the server on every incoming request. It's the right choice for user-specific dashboards, carts, live prices or constantly changing data. To enable it, you turn the cache off entirely:

const res = await fetch('https://api.example.com/prices', {
  cache: 'no-store',
});

You can achieve the same effect at the route-segment level. Adding the line below at the top of the page re-fetches all data on that route for every request:

export const dynamic = 'force-dynamic';

The route also switches to dynamic automatically when you read cookies(), headers() or searchParams, because the output can differ per user.

ISR: static speed + periodic freshness

Incremental Static Regeneration (ISR) bridges the speed of static rendering and the freshness of SSR. The page is served statically, but once the interval you set expires it is regenerated in the background. So users always get a fast cached version, and the content updates with a reasonable delay.

// Refresh once every 60 seconds
const res = await fetch('https://api.example.com/products', {
  next: { revalidate: 60 },
});

You can also define this at the segment level:

export const revalidate = 60;

ISR is tailor-made for e-commerce listings, news feeds or dashboards that change a few times an hour: even with a million requests per minute, regeneration only triggers once, when the interval expires.

On-demand revalidation: just-in-time updates

Instead of a fixed interval, you may want to clear the cache when the content actually changes. Next.js offers two methods for this: by tag (revalidateTag) and by path (revalidatePath). First you mark the fetch with a tag:

await fetch('https://api.example.com/products', {
  next: { tags: ['products'] },
});

Then, inside a Server Action or Route Handler — for example when a product is updated from the admin panel — you invalidate that tag:

import { revalidateTag } from 'next/cache';

revalidateTag('products');

This keeps the page as fast as static while reflecting changes the moment they happen. For projects with a CMS or admin panel, this gives much finer control than time-based ISR.

Choosing the right strategy

  • Static: rarely changing content, highest performance. cache: 'force-cache'.
  • ISR: frequent updates that needn't be instant. next: { revalidate: N }.
  • SSR: user-specific data or data changing every second. cache: 'no-store'.
  • On-demand: event-triggered updates. tags + revalidateTag.

A practical tip: start with ISR for most pages and don't make everything force-dynamic unless you truly have to. Needless dynamism wastes the biggest advantage Next.js gives you — the cache.

Frequently Asked Questions

Why is fetch caching different between Next.js 14 and 15?

In Next.js 15, fetch is not cached by default; you opt into caching explicitly with cache: 'force-cache' or revalidate. In Next.js 14 it was the opposite — the default was cached. Checking this difference when upgrading older projects avoids unexpected surprises.

How do I fetch data inside a Client Component?

Server caching doesn't apply in Client Components; you fetch the classic way, for example with useEffect or libraries like SWR / React Query. Whenever possible, fetching in a Server Component and passing the data down as a prop is usually more efficient.

Can I use revalidate and force-dynamic together?

No, the two conflict. force-dynamic renders the page on every request and ignores the cache, so the revalidate interval becomes meaningless. Choose revalidate for periodic refresh, or force-dynamic for full dynamism.

Want to set up a data-fetching and caching strategy in your Next.js project? With the right render model, a site can be both fast and up to date. Let's look at it together: get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için