fetch() Caching
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
async function getCachedData() {
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache'
});
return res.json();
}
async function getFreshData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return res.json();
}
async function getRevalidatedData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
});
return res.json();
}
Cache Options
const fetchOptions = {
cache: 'default' | 'force-cache' | 'no-store' | 'only-cache' | 'reload',
next: {
revalidate: 3600,
tags: ['posts', 'user'],
revalidateTag: 'posts'
}
};
const data = await fetch('/api/posts', {
cache: 'force-cache',
next: {
revalidate: 60,
tags: ['posts']
}
});
Basic Static Generation
export async function getStaticProps({ params }) {
const post = await getPost(params.id);
return {
props: {
post
},
revalidate: 3600
};
}
export async function getStaticPaths() {
const posts = await getPosts();
const paths = posts.map((post) => ({
params: { id: post.id.toString() }
}));
return {
paths,
fallback: 'blocking'
};
}
Incremental Static Regeneration (ISR)
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data');
const posts = await data.json();
return {
props: {
posts
},
revalidate: 60,
notFound: false
};
}
export async function getStaticProps() {
const posts = await getPosts();
return {
props: { posts },
revalidate: 3600,
tags: ['posts']
};
}
App Router Caching
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
{posts.map(post => (
<Post key={post.id} post={post} />
))}
</div>
);
}
Route Segment Config
export const revalidate = 3600;
export const dynamic = 'force-static';
export const fetchCache = 'force-cache';
export const dynamic = 'force-dynamic';
export const fetchCache = 'force-no-store';
Cache Tags
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] }
});
return res.json();
}
import { revalidateTag } from 'next/cache';
export async function POST(request) {
const post = await createPost(request.body);
revalidateTag('posts');
return Response.json({ post });
}
On-Demand Revalidation
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const { path, tag } = await request.json();
if (path) {
revalidatePath(path);
}
if (tag) {
revalidateTag(tag);
}
return Response.json({ revalidated: true });
}
await fetch('/api/revalidate', {
method: 'POST',
body: JSON.stringify({
path: '/posts',
tag: 'posts'
})
});
Time-Based Revalidation
export async function getStaticProps() {
return {
props: { data },
revalidate: 3600
};
}
export const revalidate = 300;
export const revalidate = false;
Conditional Revalidation
export async function getStaticProps() {
const data = await fetchData();
const revalidateTime = data.isStale ? 60 : 3600;
return {
props: { data },
revalidate: revalidateTime
};
}
Manual Cache Invalidation
import { revalidatePath } from 'next/cache';
export async function POST(request) {
revalidatePath('/posts');
revalidatePath('/posts', 'layout');
revalidatePath('/posts', 'page');
}
import { revalidateTag } from 'next/cache';
export async function POST(request) {
revalidateTag('posts');
revalidateTag('posts');
revalidateTag('users');
}
Webhook-Based Invalidation
import { revalidateTag } from 'next/cache';
export async function POST(request) {
const body = await request.json();
if (body.event === 'post.created') {
revalidateTag('posts');
}
if (body.event === 'user.updated') {
revalidateTag('users');
}
return Response.json({ success: true });
}
Cache Headers
export async function GET(request) {
const data = await getData();
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400'
}
});
}
const cacheHeaders = {
'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400'
};
Optimistic Updates
'use client';
import { useOptimistic } from 'react';
export default function PostForm() {
const [optimisticPosts, addOptimisticPost] = useOptimistic(
posts,
(state, newPost) => [...state, newPost]
);
async function handleSubmit(formData) {
const newPost = { title: formData.get('title'), pending: true };
addOptimisticPost(newPost);
await createPost(formData);
}
return (
<form action={handleSubmit}>
{/* Form fields */}
</form>
);
}
Streaming and Suspense
import { Suspense } from 'react';
export default function PostsPage() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostsList />
</Suspense>
</div>
);
}
async function PostsList() {
const posts = await getPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Next.js Config
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ['@prisma/client'],
optimizeCss: true,
optimizePackageImports: ['lodash', 'react-icons']
},
images: {
minimumCacheTTL: 60,
formats: ['image/webp', 'image/avif']
}
};
module.exports = nextConfig;
Environment Variables
# Cache configuration
NEXT_CACHE_MAX_AGE=3600
NEXT_CACHE_TAGS=posts,users,products
# Revalidation
NEXT_REVALIDATE_TIME=3600
NEXT_REVALIDATE_SECRET=your-secret-key
Cache Status
export async function GET(request) {
const { searchParams } = new URL(request.url);
const debug = searchParams.get('debug');
if (debug === 'cache') {
return Response.json({
cache: 'hit',
timestamp: new Date().toISOString(),
revalidated: false
});
}
const data = await getData();
return Response.json(data);
}
Cache Headers Debug
export async function GET(request) {
const data = await getData();
return Response.json(data, {
headers: {
'X-Cache-Status': 'HIT',
'X-Cache-Timestamp': new Date().toISOString(),
'X-Cache-TTL': '3600'
}
});
}