TL;DR:
  • Server Components fetch on the server — fetch config controls rendering.
  • Use cache: 'force-cache' or default caching for static content.
  • Use cache: 'no-store' or dynamic = 'force-dynamic' for fresh per-request data.
  • Use next.revalidate for ISR and scheduled updates.

Learn data fetching and caching strategies for Next.js 16 (App Router). This guide explains static vs dynamic rendering, fetch caching options, revalidation (ISR), no-store mode, streaming, and the SEO implications of each approach.

In Next.js 16, data fetching determines how pages are rendered and cached — not just how you call an API.

It directly controls:

  • Whether a page is static or dynamic
  • Whether it is cached or not
  • How often it updates
  • How fast it loads
  • How it affects SEO

In simple words: In Next.js 16, your fetch() configuration (and related exports) decide the rendering and caching strategy. Understanding caching and revalidation is the key to building fast, SEO-friendly Next.js apps.


Server First Model

In Next.js 16:

  • Components are Server Components by default
  • Data fetching happens on the server
  • The result is sent as ready HTML
  • No need for useEffect for initial data.

export default async function UsersPage() {

  const res = await fetch("https://api.example.com/users")
  const users = await res.json()

  return (
    <div>
      {users.map((user: any) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

Cached Rendering in Next.js

By default, Next.js caches data fetched in Server Components. This means that if the same data is requested multiple times, it is served from the cache instead of being fetched again.

This is called Static Rendering.

Static Rendering


export default async function UsersPage() {

  const res = await fetch("https://api.example.com/users",{
    cache: "force-cache"
  });
  const users = await res.json()

  return (
    <div>
      {users.map((user: any) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

Dynamic Rendering

Dynamic rendering is used when data changes frequently and you want to fetch fresh data on each request.


export default async function UsersPage() {

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

  return (
    <div>
      {users.map((user: any) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

Revalidation (Incremental Static Regeneration)

Revalidation allows you to update static content without rebuilding the entire site. This is useful for updating content that changes frequently. For example, if you have a blog with static pages that are generated at build time, you can revalidate them on a schedule or when new content is published.


export default async function UsersPage() {

  const res = await fetch("https://api.example.com/users",{
    next: {
      revalidate: 60        // Revalidate every 60 seconds
    }
  });
  const users = await res.json()

  return (
    <div>
      {users.map((user: any) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}



Forcing Dynamic Rendering

If you want entire page dynamic:

 
export const dynamic = "force-dynamic"

or

 
export const revalidate = 0

This disables static rendering for the entire page, forcing it to be dynamic on every request.


Streaming and Suspense

If you're using React 18 or later, Next.js supports streaming and suspense for better user experience. You can use streaming to progressively render content as it becomes available.


import { Suspense } from "react"

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>

      <Suspense fallback={<p>Loading...</p>}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

Parallel Data Fetching

Next.js allows you to fetch multiple data sources in parallel using the Promise.all pattern. This is especially useful when you need to combine data from different APIs or databases.


export default async function Page() {
  const [posts, users] = await Promise.all([
    fetch("https://api.example.com/posts"),
    fetch("https://api.example.com/users")
  ])

  const postsData = await posts.json()
  const usersData = await users.json()

  return (
    <div>
      <h2>Posts</h2>
      {postsData.map((post: any) => (
        <p key={post.id}>{post.title}</p>
      ))}
      <h2>Users</h2>
      {usersData.map((user: any) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  )
}

Conclusion

In Next.js 16, data fetching is a powerful tool that directly influences your rendering strategy. By understanding how to configure fetch caching and revalidation, you can optimize your application's performance and user experience. Whether you need static content that updates periodically or dynamic content that changes on every request, Next.js provides the flexibility to meet your needs.

  • Use static rendering for content that doesn't change often.
  • Use dynamic rendering for content that changes frequently or is user-specific.
  • Configure revalidation intervals to control how often static content updates.
  • Leverage streaming and suspense for better user experience.