乐闻世界logo
搜索文章和话题

How does Next.js caching mechanism work?

2月17日 23:30

Next.js caching mechanism is the core of its performance optimization. Understanding these caching strategies is crucial for building high-performance applications.

Caching Layers

Next.js provides multiple layers of caching:

  1. Build-time caching: Static generation and ISR
  2. Request-time caching: fetch API caching
  3. CDN caching: Edge network caching
  4. Browser caching: Client-side caching
  5. Data caching: React Query, SWR, etc.

Fetch API Caching

Cache Options

Next.js extends the fetch API with various caching options.

javascript
// Default caching (force-cache) async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'force-cache', // Default }).then(r => r.json()); return <div>{data.content}</div>; } // No caching (no-store) async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'no-store', }).then(r => r.json()); return <div>{data.content}</div>; } // Validate cache (no-cache) async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'no-cache', // Validate cache every time }).then(r => r.json()); return <div>{data.content}</div>; } // Only use if cached (only-if-cached) async function Page() { const response = await fetch('https://api.example.com/data', { cache: 'only-if-cached', }); if (!response.ok) { return <div>Loading...</div>; } const data = await response.json(); return <div>{data.content}</div>; }

Next.js Extended Options

javascript
// revalidate: Set revalidation time (seconds) async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 60, // Revalidate every 60 seconds }, }).then(r => r.json()); return <div>{data.content}</div>; } // tags: For on-demand revalidation async function Page() { const data = await fetch('https://api.example.com/data', { next: { tags: ['posts', 'latest'], }, }).then(r => r.json()); return <div>{data.content}</div>; } // Combine usage async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'force-cache', next: { revalidate: 3600, tags: ['data'], }, }).then(r => r.json()); return <div>{data.content}</div>; }

On-demand Revalidation

revalidatePath

javascript
import { revalidatePath } from 'next/cache'; 'use server'; export async function updatePost(postId: string, formData: FormData) { const title = formData.get('title'); const content = formData.get('content'); await db.post.update({ where: { id: postId }, data: { title, content }, }); // Revalidate specific path revalidatePath('/posts'); revalidatePath(`/posts/${postId}`); return { success: true }; }

revalidateTag

javascript
import { revalidateTag } from 'next/cache'; 'use server'; export async function createPost(formData: FormData) { const title = formData.get('title'); const content = formData.get('content'); await db.post.create({ data: { title, content }, }); // Revalidate all caches with 'posts' tag revalidateTag('posts'); return { success: true }; } // Data fetching with tags async function Page() { const posts = await fetch('https://api.example.com/posts', { next: { tags: ['posts'], revalidate: 3600, }, }).then(r => r.json()); return <PostList posts={posts} />; }

Static Generation Caching

getStaticProps Caching

javascript
export async function getStaticProps() { const data = await fetchData(); return { props: { data }, revalidate: 3600, // ISR: Regenerate every hour }; }

getStaticPaths Caching

javascript
export async function getStaticPaths() { const posts = await getAllPosts(); return { paths: posts.map(post => ({ params: { slug: post.slug } })), fallback: 'blocking', // Or false or true }; } export async function getStaticProps({ params }) { const post = await getPostBySlug(params.slug); return { props: { post }, revalidate: 86400, // Regenerate every day }; }

Server Component Caching

Default Cache Behavior

javascript
// By default, fetch requests are cached async function Page() { const data = await fetch('https://api.example.com/data') .then(r => r.json()); return <div>{data.content}</div>; }

Disable Caching

javascript
// Disable caching async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'no-store', }).then(r => r.json()); return <div>{data.content}</div>; }

Dynamic Data Fetching

javascript
// For dynamic data, disable caching async function UserDashboard({ params }) { const user = await fetch(`https://api.example.com/user/${params.id}`, { cache: 'no-store', }).then(r => r.json()); return <Dashboard user={user} />; }

Client-side Caching

React Query Caching

javascript
'use client'; import { useQuery, useQueryClient } from '@tanstack/react-query'; export default function DataComponent() { const queryClient = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ['data'], queryFn: () => fetch('/api/data').then(r => r.json()), staleTime: 60000, // Data is considered fresh for 60 seconds cacheTime: 300000, // Cache is valid for 5 minutes }); const handleRefresh = () => { // Manually refresh data queryClient.invalidateQueries({ queryKey: ['data'] }); }; if (isLoading) return <div>Loading...</div>; return ( <div> <div>{data.content}</div> <button onClick={handleRefresh}>Refresh</button> </div> ); }

SWR Caching

javascript
'use client'; import useSWR from 'swr'; const fetcher = (url) => fetch(url).then(r => r.json()); export default function DataComponent() { const { data, error, isLoading, mutate } = useSWR( '/api/data', fetcher, { revalidateOnFocus: false, // Don't revalidate on window focus revalidateOnReconnect: false, // Don't revalidate on reconnect dedupingInterval: 60000, // Deduplicate requests within 60 seconds refreshInterval: 0, // Don't auto-refresh } ); const handleRefresh = () => { // Manually refresh data mutate(); }; if (isLoading) return <div>Loading...</div>; if (error) return <div>Error</div>; return ( <div> <div>{data.content}</div> <button onClick={handleRefresh}>Refresh</button> </div> ); }

CDN Caching

Vercel CDN

When deploying to Vercel, static assets are automatically cached on CDN.

javascript
// next.config.js module.exports = { // Static assets are cached on Vercel CDN output: 'standalone', };

Custom CDN Headers

javascript
// app/api/data/route.js export async function GET() { const data = await fetchData(); return Response.json(data, { headers: { 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400', 'CDN-Cache-Control': 'public, s-maxage=3600', }, }); }

Browser Caching

Set Cache Headers

javascript
// app/api/data/route.js export async function GET() { const data = await fetchData(); return Response.json(data, { headers: { // Strong cache: 1 hour 'Cache-Control': 'public, max-age=3600, immutable', // Negotiation cache: ETag 'ETag': generateETag(data), // Last modified time 'Last-Modified': new Date().toUTCString(), }, }); } function generateETag(data) { const crypto = require('crypto'); return crypto.createHash('md5').update(JSON.stringify(data)).digest('hex'); }

Conditional Requests

javascript
// app/api/data/route.js export async function GET(request) { const data = await fetchData(); const etag = generateETag(data); // Check If-None-Match header const ifNoneMatch = request.headers.get('if-none-match'); if (ifNoneMatch === etag) { return new Response(null, { status: 304 }); } return Response.json(data, { headers: { 'ETag': etag, 'Cache-Control': 'public, max-age=3600', }, }); }

Advanced Caching Strategies

Layered Caching

javascript
// Layer 1: Browser cache // Layer 2: CDN cache // Layer 3: Next.js cache // Layer 4: Data source async function Page() { const data = await fetch('https://api.example.com/data', { // Next.js cache cache: 'force-cache', next: { revalidate: 3600, tags: ['data'], }, }).then(r => r.json()); return <div>{data.content}</div>; } // API route sets CDN cache export async function GET() { const data = await fetchData(); return Response.json(data, { headers: { // CDN cache 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400', }, }); }

Smart Cache Invalidation

javascript
'use server'; import { revalidateTag, revalidatePath } from 'next/cache'; export async function updateData(id: string, formData: FormData) { const data = await updateDataInDB(id, formData); // Choose invalidation strategy based on data type if (data.type === 'post') { revalidateTag('posts'); revalidatePath('/posts'); } else if (data.type === 'user') { revalidateTag('users'); revalidatePath('/users'); } return { success: true }; }

Cache Warming

javascript
// app/api/warmup/route.js export async function GET() { const urls = [ 'https://example.com/api/data', 'https://example.com/api/posts', 'https://example.com/api/users', ]; // Warm up cache in parallel await Promise.all( urls.map(url => fetch(url)) ); return Response.json({ message: 'Cache warmed up' }); }

Real-world Use Cases

1. Blog Post Caching

javascript
// app/blog/[slug]/page.js async function BlogPost({ params }) { const post = await fetch(`https://api.example.com/posts/${params.slug}`, { next: { revalidate: 86400, // Revalidate every day tags: [`post-${params.slug}`, 'posts'], }, }).then(r => r.json()); return <PostContent post={post} />; } // Revalidate when updating post 'use server'; import { revalidateTag } from 'next/cache'; export async function updatePost(postId: string, formData: FormData) { const post = await updatePostInDB(postId, formData); revalidateTag(`post-${post.slug}`); revalidateTag('posts'); return { success: true }; }

2. E-commerce Product Caching

javascript
// app/products/[id]/page.js async function ProductPage({ params }) { const product = await fetch(`https://api.example.com/products/${params.id}`, { next: { revalidate: 3600, // Revalidate every hour tags: [`product-${params.id}`, 'products'], }, }).then(r => r.json()); return <ProductDetails product={product} />; } // Product list page async function ProductListPage() { const products = await fetch('https://api.example.com/products', { next: { revalidate: 600, // Revalidate every 10 minutes tags: ['products'], }, }).then(r => r.json()); return <ProductList products={products} />; }

3. User Data Caching

javascript
// app/dashboard/page.js async function DashboardPage() { const session = await auth(); // User data not cached (dynamic data) const user = await fetch(`https://api.example.com/user/${session.user.id}`, { cache: 'no-store', }).then(r => r.json()); // Stats data can be cached const stats = await fetch(`https://api.example.com/stats/${session.user.id}`, { next: { revalidate: 300, // Revalidate every 5 minutes tags: [`stats-${session.user.id}`], }, }).then(r => r.json()); return <Dashboard user={user} stats={stats} />; }

4. Real-time Data Caching

javascript
// app/live/page.js async function LivePage() { // Real-time data not cached const liveData = await fetch('https://api.example.com/live', { cache: 'no-store', }).then(r => r.json()); return <LiveData data={liveData} />; } // Use SWR for client-side polling 'use client'; import useSWR from 'swr'; export default function LiveDataComponent() { const { data } = useSWR( '/api/live', fetcher, { refreshInterval: 5000, // Refresh every 5 seconds revalidateOnFocus: true, } ); return <div>{data?.content}</div>; }

Best Practices

1. Choose Caching Strategy Based on Data Type

javascript
// ✅ Good practice: Static content uses long cache async function Page() { const data = await fetch('https://api.example.com/static-data', { next: { revalidate: 86400, // 1 day }, }).then(r => r.json()); return <div>{data.content}</div>; } // ✅ Good practice: Dynamic content disables cache async function Page() { const data = await fetch('https://api.example.com/dynamic-data', { cache: 'no-store', }).then(r => r.json()); return <div>{data.content}</div>; } // ❌ Bad practice: All data uses same caching strategy async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600, }, }).then(r => r.json()); return <div>{data.content}</div>; }

2. Use Tags for Precise Cache Invalidation

javascript
// ✅ Good practice: Use tags async function Page() { const data = await fetch('https://api.example.com/data', { next: { tags: ['posts', 'latest'], }, }).then(r => r.json()); return <div>{data.content}</div>; } // Invalidate only related tags when updating revalidateTag('posts'); // ❌ Bad practice: Invalidate entire path revalidatePath('/');

3. Set Reasonable Revalidation Time

javascript
// ✅ Good practice: Set based on update frequency async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600, // 1 hour }, }).then(r => r.json()); return <div>{data.content}</div>; } // ❌ Bad practice: Set too short revalidation time async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 10, // 10 seconds (too short) }, }).then(r => r.json()); return <div>{data.content}</div>; }

4. Monitor Cache Hit Rate

javascript
// lib/cacheMonitor.js export function logCacheHit(key: string, hit: boolean) { console.log(`Cache ${hit ? 'hit' : 'miss'}: ${key}`); // Send to monitoring service if (typeof window !== 'undefined') { fetch('/api/cache-log', { method: 'POST', body: JSON.stringify({ key, hit }), }); } } // Usage async function Page() { const key = 'data'; const data = await fetch('https://api.example.com/data', { next: { tags: [key], }, }).then(r => r.json()); logCacheHit(key, true); return <div>{data.content}</div>; }

By properly using Next.js caching mechanisms, you can significantly improve application performance, reduce server load, and provide better user experience.

标签:Next.js