Skip to content

IvanTsxx/cache-components-granular

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

25 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿš€ Cache Components Demo โ€“ Next.js 16

Practical demonstration of field-level granular caching using Cache Components in Next.js 16.

๐Ÿ“„ Spanish version: Leer en Espaรฑol

Next.js React TypeScript Tailwind CSS


๐ŸŽฏ What Problem Does This Solve?

This project demonstrates how to cache individual fields of a record using different strategies in Next.js 16.

Common Scenario

You have a product with three fields that change at different rates:

  • Text (name + description): Rarely changes
  • Price: Changes occasionally
  • Stock: Must always be up to date

How do you cache each field independently?

The Solution

Each field is implemented as a separate async component, with its own query and caching strategy.

// โœ… Each field has its own strategy
<ProductText productId={id} />      // Cached for 1 week
<ProductPrice productId={id} />     // Cached for 1 hour
<Suspense>
  <ProductStock productId={id} />   // No cache (streaming)
</Suspense>

โœจ Features

  • โœ… Granular field-level caching โ€” Independent control over each data segment
  • โœ… Separated queries โ€” One query per field, automatically optimized
  • โœ… Tag-based revalidation โ€” Invalidate only what changed
  • โœ… Static shell + Streaming โ€” Instant HTML + fresh runtime data
  • โœ… Correct Suspense boundaries โ€” Clear examples of placement and reasoning
  • โœ… Interactive demo โ€” Buttons to test live revalidation
  • โœ… Static product routes โ€” generateStaticParams reinforcing educational PPR
  • โœ… Zod validation โ€” productId sanitization in Server Actions
  • โœ… Tailwind + shadcn/ui โ€” Modern, professional UI
  • โœ… TypeScript โ€” Fully type-safe
  • โœ… Mock DB with logs โ€” Observe when and how queries execute

๐Ÿ—๏ธ Architecture

ProductPage (parent - sync)
โ”‚
โ””โ”€ <Suspense>
   โ””โ”€ ProductContent (async - accesses params)
      โ”œโ”€ ProductText (async + 'use cache' + cacheLife('weeks'))
      โ”œโ”€ ProductPrice (async + 'use cache' + cacheLife('hours'))
      โ””โ”€ <Suspense>
         โ””โ”€ ProductStock (async without cache - streaming)

๐Ÿ“ฆ Installation

# Install dependencies
bun install

# Start development server
bun dev

Open: http://localhost:3000


๐ŸŽฎ How to Use

1. View the Demo

  • The home page displays a product list
  • Click any product
  • Observe colored badges indicating cache behavior

2. Inspect Network Tab

  • Open DevTools โ†’ Network
  • Disable cache
  • Navigate to a product
  • Notice:
    • Initial HTML already contains text and price
    • Stock arrives later via streaming

3. Check Server Logs

In your terminal:

[DB Query] ๐Ÿ“ getProductText - Product 1
[DB Query] ๐Ÿ’ฐ getProductPrice - Product 1
[DB Query] ๐Ÿ“ฆ getProductStock - Product 1

4. Test Revalidation

  • Click "Revalidate Price"
  • Refresh the page
  • Only the price regenerates
  • Text remains cached

๐Ÿ”‘ Key Concepts

1. Async Component = Promise

// An async component IS a promise
async function ProductStock({ productId }) {
  const stock = await db.getStock(productId);
  return <div>{stock}</div>;
}

// Suspense must wrap it in the PARENT
<Suspense fallback="Loading...">
  <ProductStock /> {/* โ† This line creates the promise */}
</Suspense>;

2. Multiple Queries vs Cache

Yes, there are multiple queries โ€” but:

  • Cached queries run at build time โ†’ static shell
  • Dynamic queries run at request time โ†’ streaming
  • Result: Improved perceived performance

3. Tag-Based Revalidation

// Cache with tag
cacheTag(`product-price-${productId}`);

// Revalidate only that field
revalidateTag(`product-price-${productId}`, "max");

๐Ÿ“– Internal Documentation

  • /docs โ€” Introduction
  • /docs/implementation โ€” Full implementation
  • /docs/concepts โ€” Core concepts
  • /docs/revalidation โ€” updateTag vs revalidateTag
  • /docs/benefits โ€” Advantages

๐Ÿ“š Resources


๐Ÿ› Troubleshooting

Error: "Uncached data was accessed outside of Suspense"

Cause: Async component without cache or Suspense

Fix: Add <Suspense> in the parent or 'use cache' inside the component


Stock does not update on refresh

Cause: Browser cache enabled

Fix: Hard refresh (Ctrl + Shift + R) or use a private window


Revalidation does not work

Cause: Incorrect tag

Fix: Ensure the tag is identical in both cache and revalidation:

cacheTag(`product-price-${productId}`);
revalidateTag(`product-price-${productId}`, "max");

๐Ÿค Contributing

Contributions are welcome:

  1. Fork the repository
  2. Create a branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to your branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

๐Ÿ“ License

MIT


๐Ÿ‘ค Author: IvanTsxx

Created to demonstrate Cache Components in Next.js 16.


๐Ÿ’ก What Youโ€™ll Learn

This project showcases advanced Next.js 16 patterns:

  • Granular caching with use cache
  • Proper Suspense boundaries
  • Runtime data handling
  • Selective tag-based revalidation
  • Static shell + Streaming (PPR)

Use this repository as a reference for real-world implementations.

Releases

No releases published

Packages

 
 
 

Contributors