Next.js 的缓存机制是如何工作的?
Next.js 的缓存机制是其性能优化的核心,理解这些缓存策略对于构建高性能应用至关重要。缓存层次Next.js 提供多个层次的缓存:构建时缓存:静态生成和 ISR请求时缓存:fetch API 缓存CDN 缓存:边缘网络缓存浏览器缓存:客户端缓存数据缓存:React Query、SWR 等Fetch API 缓存缓存选项Next.js 扩展了 fetch API,提供了多种缓存选项。// 默认缓存(force-cache)async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'force-cache', // 默认 }).then(r => r.json()); return <div>{data.content}</div>;}// 不缓存(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>;}// 验证缓存(no-cache)async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'no-cache', // 每次验证缓存 }).then(r => r.json()); return <div>{data.content}</div>;}// 仅当有缓存时使用(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 扩展选项// revalidate:设置重新验证时间(秒)async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 60, // 每 60 秒重新验证 }, }).then(r => r.json()); return <div>{data.content}</div>;}// 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>;}// 组合使用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>;}按需重新验证revalidatePathimport { 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 }, }); // 重新验证特定路径 revalidatePath('/posts'); revalidatePath(`/posts/${postId}`); return { success: true };}revalidateTagimport { 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 }, }); // 重新验证所有带有 'posts' 标签的缓存 revalidateTag('posts'); return { success: true };}// 使用标签的数据获取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} />;}静态生成缓存getStaticProps 缓存export async function getStaticProps() { const data = await fetchData(); return { props: { data }, revalidate: 3600, // ISR:每小时重新生成 };}getStaticPaths 缓存export async function getStaticPaths() { const posts = await getAllPosts(); return { paths: posts.map(post => ({ params: { slug: post.slug } })), fallback: 'blocking', // 或 false 或 true };}export async function getStaticProps({ params }) { const post = await getPostBySlug(params.slug); return { props: { post }, revalidate: 86400, // 每天重新生成 };}服务器组件缓存默认缓存行为// 默认情况下,fetch 请求会被缓存async function Page() { const data = await fetch('https://api.example.com/data') .then(r => r.json()); return <div>{data.content}</div>;}禁用缓存// 禁用缓存async function Page() { const data = await fetch('https://api.example.com/data', { cache: 'no-store', }).then(r => r.json()); return <div>{data.content}</div>;}动态数据获取// 对于动态数据,禁用缓存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} />;}客户端缓存React Query 缓存'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, // 数据在 60 秒内被认为是新鲜的 cacheTime: 300000, // 缓存在 5 分钟内有效 }); const handleRefresh = () => { // 手动刷新数据 queryClient.invalidateQueries({ queryKey: ['data'] }); }; if (isLoading) return <div>Loading...</div>; return ( <div> <div>{data.content}</div> <button onClick={handleRefresh}>Refresh</button> </div> );}SWR 缓存'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, // 窗口聚焦时不重新验证 revalidateOnReconnect: false, // 重新连接时不重新验证 dedupingInterval: 60000, // 60 秒内去重请求 refreshInterval: 0, // 不自动刷新 } ); const handleRefresh = () => { // 手动刷新数据 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 缓存Vercel CDN部署到 Vercel 时,静态资源会自动缓存到 CDN。// next.config.jsmodule.exports = { // 静态资源会被缓存到 Vercel CDN output: 'standalone',};自定义 CDN 头// app/api/data/route.jsexport 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', }, });}浏览器缓存设置缓存头// app/api/data/route.jsexport async function GET() { const data = await fetchData(); return Response.json(data, { headers: { // 强缓存:1 小时 'Cache-Control': 'public, max-age=3600, immutable', // 协商缓存:ETag 'ETag': generateETag(data), // 最后修改时间 'Last-Modified': new Date().toUTCString(), }, });}function generateETag(data) { const crypto = require('crypto'); return crypto.createHash('md5').update(JSON.stringify(data)).digest('hex');}条件请求// app/api/data/route.jsexport async function GET(request) { const data = await fetchData(); const etag = generateETag(data); // 检查 If-None-Match 头 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', }, });}高级缓存策略分层缓存// 第一层:浏览器缓存// 第二层:CDN 缓存// 第三层:Next.js 缓存// 第四层:数据源async function Page() { const data = await fetch('https://api.example.com/data', { // Next.js 缓存 cache: 'force-cache', next: { revalidate: 3600, tags: ['data'], }, }).then(r => r.json()); return <div>{data.content}</div>;}// API 路由设置 CDN 缓存export async function GET() { const data = await fetchData(); return Response.json(data, { headers: { // CDN 缓存 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400', }, });}智能缓存失效'use server';import { revalidateTag, revalidatePath } from 'next/cache';export async function updateData(id: string, formData: FormData) { const data = await updateDataInDB(id, formData); // 根据数据类型选择失效策略 if (data.type === 'post') { revalidateTag('posts'); revalidatePath('/posts'); } else if (data.type === 'user') { revalidateTag('users'); revalidatePath('/users'); } return { success: true };}缓存预热// app/api/warmup/route.jsexport async function GET() { const urls = [ 'https://example.com/api/data', 'https://example.com/api/posts', 'https://example.com/api/users', ]; // 并行预热缓存 await Promise.all( urls.map(url => fetch(url)) ); return Response.json({ message: 'Cache warmed up' });}实际应用场景1. 博客文章缓存// app/blog/[slug]/page.jsasync function BlogPost({ params }) { const post = await fetch(`https://api.example.com/posts/${params.slug}`, { next: { revalidate: 86400, // 每天重新验证 tags: [`post-${params.slug}`, 'posts'], }, }).then(r => r.json()); return <PostContent post={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. 电商产品缓存// app/products/[id]/page.jsasync function ProductPage({ params }) { const product = await fetch(`https://api.example.com/products/${params.id}`, { next: { revalidate: 3600, // 每小时重新验证 tags: [`product-${params.id}`, 'products'], }, }).then(r => r.json()); return <ProductDetails product={product} />;}// 产品列表页面async function ProductListPage() { const products = await fetch('https://api.example.com/products', { next: { revalidate: 600, // 每 10 分钟重新验证 tags: ['products'], }, }).then(r => r.json()); return <ProductList products={products} />;}3. 用户数据缓存// app/dashboard/page.jsasync function DashboardPage() { const session = await auth(); // 用户数据不缓存(动态数据) const user = await fetch(`https://api.example.com/user/${session.user.id}`, { cache: 'no-store', }).then(r => r.json()); // 统计数据可以缓存 const stats = await fetch(`https://api.example.com/stats/${session.user.id}`, { next: { revalidate: 300, // 每 5 分钟重新验证 tags: [`stats-${session.user.id}`], }, }).then(r => r.json()); return <Dashboard user={user} stats={stats} />;}4. 实时数据缓存// app/live/page.jsasync function LivePage() { // 实时数据不缓存 const liveData = await fetch('https://api.example.com/live', { cache: 'no-store', }).then(r => r.json()); return <LiveData data={liveData} />;}// 使用 SWR 进行客户端轮询'use client';import useSWR from 'swr';export default function LiveDataComponent() { const { data } = useSWR( '/api/live', fetcher, { refreshInterval: 5000, // 每 5 秒刷新 revalidateOnFocus: true, } ); return <div>{data?.content}</div>;}最佳实践1. 根据数据类型选择缓存策略// ✅ 好的做法:静态内容使用长时间缓存async function Page() { const data = await fetch('https://api.example.com/static-data', { next: { revalidate: 86400, // 1 天 }, }).then(r => r.json()); return <div>{data.content}</div>;}// ✅ 好的做法:动态内容禁用缓存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>;}// ❌ 不好的做法:所有数据使用相同缓存策略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. 使用标签进行精确的缓存失效// ✅ 好的做法:使用标签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>;}// 更新时只失效相关标签revalidateTag('posts');// ❌ 不好的做法:失效整个路径revalidatePath('/');3. 合理设置重新验证时间// ✅ 好的做法:根据更新频率设置async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600, // 1 小时 }, }).then(r => r.json()); return <div>{data.content}</div>;}// ❌ 不好的做法:设置过短的重新验证时间async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 10, // 10 秒(太短) }, }).then(r => r.json()); return <div>{data.content}</div>;}4. 监控缓存命中率// lib/cacheMonitor.jsexport function logCacheHit(key: string, hit: boolean) { console.log(`Cache ${hit ? 'hit' : 'miss'}: ${key}`); // 发送到监控服务 if (typeof window !== 'undefined') { fetch('/api/cache-log', { method: 'POST', body: JSON.stringify({ key, hit }), }); }}// 使用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>;}通过合理使用 Next.js 的缓存机制,可以显著提升应用性能,减少服务器负载,提供更好的用户体验。