Home About Skills Products Work
Projects Services Experience
Learn
Tutorials Courses Blogs Resources
Contact
Blog · Next.js

Fetching Data in Next.js: Server Components, Client Components, and Caching

Fetching Data in Next.js: Server Components, Client Components, and Caching

The single most confusing part of adopting the Next.js App Router is figuring out where data should be fetched, and why the same-looking fetch() call sometimes gets cached and sometimes doesn't. Here's the mental model that actually makes it click.

Server Components: The Default

Every component under app/ is a Server Component unless it (or a parent) declares 'use client'. Server Components render entirely on the server and can await data directly — the client never even downloads the code for how that data was fetched.

export default async function ProductPage({ params }) {
  const product = await fetch(`https://api.shop.com/products/${params.id}`).then(r => r.json());
  return <ProductDetail product={product} />;
}

Client Components: When You Need Interactivity

The moment you need useState, useEffect, or a browser-only API, add 'use client' at the top of the file. Client components can still receive server-fetched data as props from their parent — you rarely need to fetch data client-side at all for the initial render.

Caching: The Part Everyone Gets Wrong

By default, fetch() inside a Server Component is cached indefinitely (like a static page). You control this explicitly:

fetch(url)                                     // cached forever (default)
fetch(url, { cache: 'no-store' })              // never cached — fetched fresh every request
fetch(url, { next: { revalidate: 60 } })       // cached, but refreshed every 60 seconds

A dashboard showing live stock levels wants no-store. A blog post's content is a perfect fit for a long revalidate window. Getting this wrong is the #1 cause of "why is my data stale in production but fine locally" bugs.

Parallel Data Fetching

Don't await one fetch, then start the next — that's a waterfall. Kick off everything you need at once:

const [product, reviews] = await Promise.all([
  getProduct(id),
  getReviews(id),
]);

Streaming with Suspense

Wrap a slow data-dependent section in <Suspense> with a loading.tsx fallback, and Next.js streams the fast parts of the page to the browser immediately while the slow part finishes on the server — no more blocking the entire page on your slowest query.

Once caching and Server/Client boundaries click, the App Router stops feeling unpredictable and starts feeling like the framework doing exactly what you told it to.

Next.js for Beginners: App Router vs Pages Router

Next.js for Beginners: App Router vs Pages Router

What actually changed between the Pages Router and the App Router, and which one to use for a new project in 2026.

Building a Full-Stack Next.js App with a Laravel API Backend

Building a Full-Stack Next.js App with a Laravel API Backend

A real architecture for splitting a Laravel JSON API from a Next.js frontend — CORS, auth across domains, and on-demand revalidation.

Esc