DEV Community

Cover image for Next.js 16: A Deep Dive into Cache Components with Real-World Examples
Mina Golzari Dalir
Mina Golzari Dalir

Posted on

Next.js 16: A Deep Dive into Cache Components with Real-World Examples

Next.js 16 was released on October 21, 2025, and it’s not just another incremental update — it’s a paradigm shift in how we think about caching, performance, and developer experience in full-stack React apps.
One of the most powerful and practical new features? Cache Components — powered by React’s cache() and deeply integrated with Next.js’ Data Cache, Router Cache, and Partial Prerendering (PPR).
In this article, we’ll go beyond the announcement and show you 5 real-world, production-ready examples of how Cache Components solve long-standing pain points — with before/after code, performance gains, and best practices.
Let’s dive in.

1. Stop Duplicating Database Calls in Layouts & Pages Problem (Before Next.js 16)
You fetch the same data (e.g. user, announcements, nav items) in both layout.tsx and page.tsx.
Each awaittriggers a separate database or API call — even if the data is identical.

// app/layout.tsx
const announcements = await getAnnouncements(); // DB hit

// app/page.tsx
const announcements = await getAnnouncements(); // DB hit AGAIN
Enter fullscreen mode Exit fullscreen mode

Result: 2x queries, slower TTFB, wasted resources.

Solution (Next.js 16 + cache)
Wrap your data functions with cache() from React:

// lib/cms.ts
import { cache } from 'react';

export const getAnnouncements = cache(async () => {
  const res = await fetch('https://cms.example.com/announcements', {
    next: { revalidate: 300 } // 5 min stale-while-revalidate
  });
  return res.json();
});
Enter fullscreen mode Exit fullscreen mode

Now use it anywhere:

// app/layout.tsx
const announcements = await getAnnouncements(); // Cache miss → fetch

// app/page.tsx
const announcements = await getAnnouncements(); // Cache hit → instant
Enter fullscreen mode Exit fullscreen mode

Result: 1 DB call per request, even across nested components.
50%+ reduction in database load.

2. Eliminate Prop Drilling in Dashboards

Problem
You fetch user, posts, and stats in a dashboard page and pass them down:

<Header user={user} />
<Posts posts={posts} />
<Stats stats={stats} />
Enter fullscreen mode Exit fullscreen mode

Manual prop drilling → fragile, hard to scale.

Solution: Shared Cached Functions


// lib/data.ts
import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return await db.user.findUnique({ where: { id } });
});

export const getPosts = cache(async (userId: string) => {
  return await db.post.findMany({ where: { authorId: userId } });
});
Enter fullscreen mode Exit fullscreen mode

Use directly in any component:


// app/dashboard/header.tsx
const user = await getUser(session.id); // Same cache key → shared

// app/dashboard/posts.tsx
const posts = await getPosts(session.id); // Reuses result!
Enter fullscreen mode Exit fullscreen mode

No prop drilling.
Same data, same cache key → automatic deduplication.

3. Real-Time Widget? Opt Out of Caching Gracefully
Not all data should be cached.
Example: Live Stock Price

// lib/stock.ts
import { cache } from 'react';

export const getStockPrice = cache(async (symbol: string) => {
  const res = await fetch(`https://api.stock.com/${symbol}`, {
    cache: 'no-store' // Always fresh
  });
  return res.json();
});
Enter fullscreen mode Exit fullscreen mode
// components/StockWidget.tsx
const price = await getStockPrice('AAPL');
return <div>Live: ${price}</div>;
Enter fullscreen mode Exit fullscreen mode

Still memoized per render pass (if used 3 times, only 1 fetch).
But never stored in Data Cache → always fresh.

4. Hybrid Static + Dynamic Blog Posts
Use generateStaticParamsfor top 10 posts, dynamic for the rest — with cached data.

// lib/blog.ts
import { cache } from 'react';

export const getPost = cache(async (slug: string) => {
  return await db.post.findUnique({ where: { slug } });
});

export const getAllSlugs = cache(async () => {
  return (await db.post.findMany({ select: { slug: true } })).map(p => p.slug);
});
Enter fullscreen mode Exit fullscreen mode
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const slugs = await getAllSlugs();
  return slugs.slice(0, 10).map(slug => ({ slug }));
}

export default async function Post({ params }) {
  const post = await getPost(params.slug); // Cached per slug
  return <Article post={post} />;
}
Enter fullscreen mode Exit fullscreen mode

Top 10 posts: Built at deploy time → instant.
Others: On-demand, cached per slug.
All data functions: Reusable & memoized.

5. Cache Third-Party APIs (GraphQL, etc.) with unstable_cache
fetchisn’t always an option.

Example: GitHub GraphQL

// lib/github.ts
import { unstable_cache } from 'next/cache';

export const getRepos = unstable_cache(
  async (owner: string) => {
    const data = await request(
      'https://api.github.com/graphql',
      `query { repositoryOwner(login: "${owner}") { repositories(first: 10) { nodes { name } } } }`
    );
    return data.repositoryOwner.repositories.nodes;
  },
  (owner) => [`github-repos-${owner}`], // cache key
  { revalidate: 300, tags: ['github'] }
);
Enter fullscreen mode Exit fullscreen mode
// app/repos/page.tsx
const repos = await getRepos('vercel');
Enter fullscreen mode Exit fullscreen mode

Invalidate with:

revalidateTag('github')
Enter fullscreen mode Exit fullscreen mode

Migration Tip: Upgrade Safely

npx @next/codemod@latest upgrade
Enter fullscreen mode Exit fullscreen mode

Then enable Cache Components:

// next.config.ts
const nextConfig = {
  experimental: {
    cacheComponents: true,
  },
};
Enter fullscreen mode Exit fullscreen mode

No breaking changes — opt-in and gradual.

Final Thoughts
Cache Components aren’t just a new API — they’re a new mental model for building fast, scalable Next.js apps.

  • No more prop drilling
  • No more duplicate fetches
  • No more guessing about caching
  • Explicit, predictable, and composable

If you're building with Next.js, upgrade to 16 today and start wrapping your data functions in cache().
Your users (and your database) will thank you.

Try it now: https://nextjs.org/blog/next-16
Docs: https://nextjs.org/docs/app/api-reference/functions/cache

Top comments (0)