skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
93 lines (73 loc) • 3.39 kB
Markdown
- 7. Caching Strategies
- Server-side caching — 'use cache' (Next.js 16, preferred)
- Server-side caching — unstablecache (Next.js 15 and earlier)
- CDN headers
- next.config headers (long-cache hashed assets)
## 7. Caching Strategies
**The big shift (Next.js 16):** App Router caching is now opt-in via **Cache Components** and the **`'use cache'`** directive. With `cacheComponents: true`, all page/layout/route code runs at request time by default; you explicitly mark what to cache. `cacheLife`/`cacheTag` are now **stable** (no `unstable_` prefix). Prefer this on new Next.js 16 code; keep `unstable_cache` only for Next.js 15-and-earlier projects.
```tsx
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = { cacheComponents: true };
export default nextConfig;
```
```tsx
// Cache a data function. The compiler derives the cache key from the args.
import { cacheTag, cacheLife } from 'next/cache';
export async function getProducts(category: string) {
'use cache';
cacheTag('products', `category-${category}`); // invalidate via revalidateTag(...)
cacheLife('minutes'); // preset (seconds|minutes|hours|days|weeks|max) OR { stale, revalidate, expire } in seconds
return db.product.findMany({ where: { category }, orderBy: { createdAt: 'desc' } });
}
// Variants (Next.js 16):
// 'use cache' → shared, persisted across deploys/instances
// 'use cache: remote' → shared, cached at runtime in the remote/data cache
// 'use cache: private' → per-user (keyed by cookies/headers), never shared across users
```
```tsx
import { unstable_cache } from 'next/cache';
// Still works in 16 but is the legacy path; migrate to 'use cache' when you adopt
// cacheComponents. Args become part of the key; the second arg is an extra key prefix.
export const getProducts = unstable_cache(
async (category: string) => {
return db.product.findMany({ where: { category }, orderBy: { createdAt: 'desc' } });
},
['products'],
{ revalidate: 300, tags: ['products', 'category'] }
);
```
> Invalidate either style from a Server Action or the `/api/revalidate` route above with `revalidateTag('products')` / `revalidatePath('/products')`.
```tsx
// Public content
return NextResponse.json(data, {
headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' },
});
// Personalized content
return NextResponse.json(data, {
headers: { 'Cache-Control': 'private, no-store, max-age=0' },
});
```
```typescript
// next.config.ts (CommonJS next.config.js: `module.exports = { async headers() {...} }`)
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async headers() {
return [
// /_next/static/* is content-hashed → safe to cache immutably for a year.
{ source: '/_next/static/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }] },
// Only mark /fonts immutable if the filenames are hashed/versioned.
{ source: '/fonts/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }] },
];
},
};
export default nextConfig;
```
---