Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add soilmass/vibe-coding-plugin --skill "caching"
Install specific skill from multi-skill repository
# Description
>
# SKILL.md
name: caching
description: >
Next.js 15 caching layers β Request Memoization, Data Cache, Full Route Cache, Router Cache, revalidation strategies, unstable_cache for Prisma
allowed-tools: Read, Grep, Glob
Caching
Purpose
Next.js 15 cache architecture and revalidation strategies. Covers the four cache layers,
explicit cache control, and tag-based revalidation. The ONE skill for cache decisions.
When to Use
- Deciding whether to cache fetch requests
- Configuring revalidation (time-based or on-demand)
- Understanding why stale data appears
- Caching Prisma queries with
unstable_cache
When NOT to Use
- Data fetching patterns β
nextjs-data - Server-side rendering strategy β
react-server-components - CDN/edge deployment caching β
deploy
Pattern
Explicit fetch caching (NOT cached by default in Next.js 15)
// Cached with revalidation
const data = await fetch("https://api.example.com/data", {
next: { revalidate: 3600, tags: ["products"] },
});
// Force cache (static)
const staticData = await fetch("https://api.example.com/static", {
cache: "force-cache",
});
// Never cache (dynamic)
const liveData = await fetch("https://api.example.com/live", {
cache: "no-store",
});
Tag-based revalidation in Server Actions
"use server";
import { revalidateTag } from "next/cache";
export async function updateProduct() {
await db.product.update({ /* ... */ });
revalidateTag("products"); // Precise invalidation
}
Caching Prisma queries with unstable_cache
import { unstable_cache } from "next/cache";
import { db } from "@/lib/db";
const getProducts = unstable_cache(
async () => db.product.findMany(),
["products"],
{ revalidate: 3600, tags: ["products"] }
);
Request Memoization with React.cache
import { cache } from "react";
import { db } from "@/lib/db";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
revalidateTag() for granular invalidation
// Fetch with tags for precise invalidation
const posts = await fetch("/api/posts", {
next: { tags: ["posts", `user-${userId}-posts`] },
});
// Invalidate only this user's posts, not all posts
import { revalidateTag } from "next/cache";
revalidateTag(`user-${userId}-posts`);
ISR with revalidate segment config
// app/blog/[slug]/page.tsx
export const revalidate = 3600; // Revalidate at most every hour
// Pre-generate known slugs at build time
export async function generateStaticParams() {
const posts = await db.post.findMany({ select: { slug: true } });
return posts.map((post) => ({ slug: post.slug }));
}
Cache hierarchy (edge β server β database)
Request β Edge Cache (CDN, fastest)
β Server Cache (unstable_cache / fetch cache)
β Database (slowest, always fresh)
Use tags to invalidate at the right layer:
- revalidateTag() clears server cache
- CDN cache respects Cache-Control headers
revalidateTag vs revalidatePath decision matrix
| Scenario | Use | Why |
|---|---|---|
| Single resource updated | revalidateTag("post-123") |
Only invalidates caches tagged with this ID |
| Category/list changed | revalidateTag("posts") |
All fetch calls tagged "posts" refetch |
| Layout/shared data changed | revalidatePath("/dashboard") |
Invalidates all data for that route segment |
| Nuclear option | revalidatePath("/", "layout") |
Invalidates everything β avoid |
Anti-pattern
// WRONG: assuming fetch is cached by default (Next.js 14 behavior)
const data = await fetch("https://api.example.com/data");
// In Next.js 15, this is equivalent to cache: "no-store"
// Data re-fetched on EVERY request β no caching!
// WRONG: cache stampede β many requests hit origin when cache expires
// If 1000 users request the same data when cache expires, all 1000
// hit the database simultaneously.
// Use stale-while-revalidate or background revalidation to avoid this.
// WRONG: revalidatePath("/") to invalidate everything
revalidatePath("/"); // Nuclear option β clears all cached data
// Use revalidateTag("specific-tag") for surgical invalidation
Next.js 15 changed the default: fetch() is NOT cached. You must explicitly
opt in with cache: "force-cache" or next: { revalidate: N }.
Common Mistakes
- Assuming fetch is cached by default β it's NOT in Next.js 15
- Using
revalidatePath("/")whenrevalidateTag("tag")is more precise - Not adding
tagsto fetches β makes on-demand revalidation impossible - Forgetting
React.cache()for Prisma calls used in multiple components - Setting revalidation too low β causes unnecessary server load
- Cache stampede β all requests hit origin simultaneously when cache expires
Checklist
- [ ] Every
fetch()has explicit cache strategy - [ ] Cacheable data uses
tagsfor on-demand revalidation - [ ] Prisma queries wrapped in
unstable_cachewhere appropriate - [ ]
React.cache()used for request-level deduplication - [ ] Server Actions call
revalidateTag/revalidatePathafter mutations - [ ]
revalidateTag()preferred overrevalidatePath()for precision - [ ] Static pages use
generateStaticParams+revalidatesegment config
Composes With
nextjs-dataβ caching is applied to data fetching patternsreact-server-actionsβ actions trigger revalidationprismaβ Prisma queries needunstable_cachewrapperperformanceβ cache strategies directly impact load times
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.