Smart caching
Every page load currently calls Claude twice—once for the summary, once for insights. That's ~$0.004 per page view. With caching, you generate once and reuse the result for all subsequent requests. Same great UX, 97% cost reduction.
Outcome
Cache AI-generated summaries and insights using Next.js "use cache" directive to serve instant responses and reduce API costs by 97%.
Fast Track
- Enable
cacheComponentsinnext.config.ts - Add
"use cache"directive to AI functions withcacheLife("hours") - Add
cacheTagfor on-demand invalidation - Test first load (generates) vs subsequent loads (cached)
Hands-on Exercise 3.1
Add caching to AI functions to improve performance and reduce costs:
Requirements:
- Enable Cache Components in
next.config.ts - Add
"use cache"directive tosummarizeReviews - Add
"use cache"directive togetReviewInsights - Use
cacheLife("hours")for 1-hour cache duration - Add
cacheTagfor targeted cache invalidation - Verify caching behavior in development
Implementation hints:
"use cache"goes at the top of the function bodycacheLifeaccepts built-in profiles:"seconds","minutes","hours","days","weeks","max"cacheTagenables on-demand invalidation withrevalidateTag()- First request generates, subsequent requests are instant
Understanding Next.js 16 Caching
Next.js 16 provides the "use cache" directive for declarative caching:
import { cacheLife, cacheTag } from "next/cache";
export async function getData(id: string) {
"use cache";
cacheLife("hours");
cacheTag(`data-${id}`);
// Expensive operation - only runs on cache miss
return await fetchData(id);
}How it works:
- First call: Executes function, caches result
- Subsequent calls: Returns cached result instantly
- After cache lifetime expires: Background regeneration on next request
- Manual invalidation: Use
revalidateTag()to clear specific caches
Benefits:
- Instant response times (no AI call)
- Reduced API costs (only regenerate periodically)
- Fresh content (automatic revalidation)
- Simple syntax (just add the directive)
Step 1: Enable Cache Components
Update next.config.ts (or create it):
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;This enables the "use cache" directive across your application.
Step 2: Cache Summary Function
Update lib/ai-summary.ts:
import { generateText, generateObject } from "ai";
import { cacheLife, cacheTag } from "next/cache";
import { Product, ReviewInsights, ReviewInsightsSchema } from "./types";
export async function summarizeReviews(product: Product): Promise<string> {
"use cache";
cacheLife("hours");
cacheTag(`product-summary-${product.slug}`);
const averageRating =
product.reviews.reduce((acc, review) => acc + review.stars, 0) /
product.reviews.length;
const prompt = `Write a summary of the reviews for the ${
product.name
} product. The product's average rating is ${averageRating} out of 5 stars.
Your goal is to highlight the most common themes and sentiments expressed by customers.
If multiple themes are present, try to capture the most important ones.
If no patterns emerge but there is a shared sentiment, capture that instead.
Try to use natural language and keep the summary concise.
Use a maximum of 4 sentences and 30 words.
Don't include any word count or character count.
No need to reference which reviews you're summarizing.
Do not reference the star rating in the summary.
Start the summary with "Customers like…" or "Customers mention…"
Here are 3 examples of good summaries:
Example 1: Customers like the quality, space, fit and value of the sport equipment bag case. They mention it's heavy duty, has lots of space and pockets, and can fit all their gear. They also appreciate the portability and appearance. That said, some disagree on the zipper.
Example 2: Customers like the quality, ease of installation, and value of the transport rack. They mention that it holds on to everything really well, and is reliable. Some complain about the wind noise, saying it makes a whistling noise at high speeds. Opinions are mixed on fit, and performance.
Example 3: Customers like the quality and value of the insulated water bottle. They say it keeps drinks cold for hours and the lid seals well. Some customers have different opinions on size and durability.
Hit the following tone based on rating:
- 1-2 stars: negative
- 3 stars: neutral
- 4-5 stars: positive
The customer reviews to summarize are as follows:
${product.reviews
.map((review, i) => `Review ${i + 1}:\n${review.review}`)
.join("\n\n")}`;
try {
const { text } = await generateText({
model: "anthropic/claude-sonnet-4.5",
prompt,
maxOutputTokens: 1000,
temperature: 0.75,
});
// Clean up the response
return text
.trim()
.replace(/^"/, "")
.replace(/"$/, "")
.replace(/[\[\(]\d+ words[\]\)]/g, "");
} catch (error) {
console.error("Failed to generate summary:", error);
throw new Error("Unable to generate review summary. Please try again.");
}
}What changed:
- Added
"use cache"directive at function start - Added
cacheLife("hours")for 1-hour cache duration - Added
cacheTagwith product slug for targeted invalidation
Step 3: Cache Insights Function
Update the getReviewInsights function in the same file:
export async function getReviewInsights(
product: Product
): Promise<ReviewInsights> {
"use cache";
cacheLife("hours");
cacheTag(`product-insights-${product.slug}`);
const averageRating =
product.reviews.reduce((acc, review) => acc + review.stars, 0) /
product.reviews.length;
const prompt = `Analyze the following customer reviews for the ${product.name} product (average rating: ${averageRating}/5).
Extract:
1. Pros: 3-5 positive aspects customers appreciate
2. Cons: 3-5 negative aspects or concerns mentioned
3. Themes: 3-5 key themes that emerge across reviews
Be specific and concise. Each item should be 3-7 words.
Reviews:
${product.reviews
.map(
(review, i) => `Review ${i + 1} (${review.stars} stars):\n${review.review}`
)
.join("\n\n")}`;
try {
const { object } = await generateObject({
model: "anthropic/claude-sonnet-4.5",
schema: ReviewInsightsSchema,
prompt,
});
return object;
} catch (error) {
console.error("Failed to extract insights:", error);
throw new Error("Unable to extract review insights. Please try again.");
}
}Both functions now:
- Cache results for 1 hour (
cacheLife("hours")) - Use product-specific tags for invalidation
- Return instantly on subsequent requests
- Automatically revalidate in background
Try It
-
Clear any existing cache and restart:
rm -rf .next pnpm dev -
Visit a product page:
http://localhost:3000/mower -
Check terminal output (first load):
GET /mower 200 in 4.2sTiming:
- Summary generation: ~2s
- Insights generation: ~2s
- Total: ~4s
- Cost: ~$0.004
-
Refresh the page (cached load):
GET /mower 200 in 52msTiming:
- Summary: instant (cached)
- Insights: instant (cached)
- Total: ~50ms
- Cost: $0.00
That's 98.7% faster and free!
-
Try different products:
/ecoBright- First load: ~4s, subsequent: ~50ms/aquaHeat- First load: ~4s, subsequent: ~50ms
Each product has its own cache entry.
Cache Invalidation
Manually invalidate cache when reviews change:
"use server";
import { revalidateTag } from "next/cache";
export async function invalidateProductCache(productSlug: string) {
revalidateTag(`product-summary-${productSlug}`);
revalidateTag(`product-insights-${productSlug}`);
}Use cases:
- New review submitted → invalidate that product's cache
- Product updated → invalidate cache
- Admin triggers refresh → invalidate cache
Built-in Cache Profiles
Next.js provides several built-in cacheLife profiles:
| Profile | Stale (client) | Revalidate (server) | Expire |
|---|---|---|---|
"seconds" | 0 | 1s | 60s |
"minutes" | 5m | 1m | 1h |
"hours" | 5m | 1h | 1d |
"days" | 5m | 1d | 1w |
"weeks" | 5m | 1w | 30d |
"max" | 5m | 30d | 1y |
For AI summaries: "hours" is a good default—fresh enough for new reviews, cached enough to save costs.
Performance Comparison
Before caching:
Page Load:
├─ Generate summary: 2.1s ($0.002)
├─ Generate insights: 2.0s ($0.002)
└─ Total: 4.1s ($0.004)
10,000 page views/month:
└─ Cost: $40.00
After caching (1 hour revalidation):
First Load (1 per hour per product):
├─ Generate summary: 2.1s ($0.002)
├─ Generate insights: 2.0s ($0.002)
└─ Total: 4.1s ($0.004)
Cached Loads (all subsequent):
├─ Return summary: 5ms ($0.00)
├─ Return insights: 5ms ($0.00)
└─ Total: 50ms ($0.00)
10,000 page views/month (1 hour cache):
├─ Cache hits: ~9,760 loads ($0.00)
├─ Cache misses: ~240 loads ($0.96)
└─ Total: $0.96 (97.6% reduction)
Benefits:
- 98.7% faster response time
- 97.6% cost reduction
- Same UX (users see instant results)
- Fresh data (revalidates hourly)
Production Deployment
When you deploy to Vercel, caching works across all serverless function invocations:
- First user visits
/mower→ generates summary and insights (~4s) - All subsequent users get cached results (~50ms)
- After 1 hour → background regeneration on next request
- Users always get instant responses (cached or fresh)
Vercel's distributed cache:
- Shared across all function instances
- Persistent across deployments
- Edge-cached for global performance
Commit
git add next.config.ts lib/ai-summary.ts
git commit -m "feat(ai): add caching to reduce costs and improve performance"
git pushDone-When
cacheComponentsenabled innext.config.tssummarizeReviewsuses"use cache"directivegetReviewInsightsuses"use cache"directive- Cache tags added for both functions
- Cache lifetime set to 1 hour with
cacheLife("hours") - Verified first load generates content
- Verified subsequent loads return instantly
- Cache invalidation function created
- Performance improvement measured (~98% faster)
- Cost reduction achieved (~97% savings)
What's next
You've built a complete AI-powered review summary feature with caching for cost reduction. Next, you'll prepare for production by handling AI failures gracefully and configuring model fallbacks.
Sources:
Was this helpful?