05-服务端渲染与元框架——04. 数据获取 - fetch、缓存与重新验证
2026/8/6 4:17:35 网站建设 项目流程

04. 数据获取 - fetch、缓存与重新验证

概述

Next.js 扩展了原生 fetch API,添加了强大的缓存和重新验证能力。在 Server Components 中,你可以直接使用 async/await 获取数据,并精细控制每个请求的缓存行为。

维度内容
WhatNext.js 扩展了原生 fetch,支持缓存和重新验证
Why简化数据获取,提供细粒度的缓存控制
When在 Server/Client Component 中获取数据
Where组件内部、布局、路由处理器中
Who需要数据获取的开发者
Howfetch(url, { cache: 'force-cache', next: { revalidate: 3600 } })

1. 数据获取基础

1.1 在 Server Component 中获取数据

// app/users/page.js // Server Component 可以直接使用 async/await async function UsersPage() { const users = await fetch('https://api.example.com/users').then(res => res.json()); return ( <div> <h1>用户列表</h1> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); } export default UsersPage;

1.2 在 Client Component 中获取数据

'use client'; import { useState, useEffect } from 'react'; export default function ClientUsersPage() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/users') .then(res => res.json()) .then(data => { setUsers(data); setLoading(false); }); }, []); if (loading) return <div>加载中...</div>; return ( <div> <h1>用户列表</h1> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); }

2. 缓存策略

2.1 静态缓存 (force-cache)

// 默认行为:缓存数据,相同请求只执行一次 async function StaticPage() { // 这个请求会被缓存 const data = await fetch('https://api.example.com/static-data', { cache: 'force-cache', }).then(res => res.json()); return <div>{data.content}</div>; }

2.2 动态数据 (no-store)

// 每次都重新获取,不缓存 async function DynamicPage() { const data = await fetch('https://api.example.com/live-data', { cache: 'no-store', // 每次请求都重新获取 }).then(res => res.json()); return <div>{data.currentValue}</div>; }

2.3 重新验证 (ISR)

// 缓存数据,但每隔一段时间重新验证 async function ISRPage() { const data = await fetch('https://api.example.com/semi-static', { next: { revalidate: 3600 }, // 3600 秒后重新验证 }).then(res => res.json()); return <div>{data.content}</div>; }

2.4 缓存策略对比

策略配置行为适用场景
静态cache: 'force-cache'永久缓存不变的内容
动态cache: 'no-store'每次重新获取实时数据
ISRnext: { revalidate: n }缓存 + 定期更新准实时数据

3. 数据缓存

3.1 全局缓存配置

// app/layout.js import { unstable_noStore as noStore } from 'next/cache'; export default function Layout({ children }) { // 标记整个布局为动态 noStore(); return <div>{children}</div>; }

3.2 路由段配置

// app/products/page.js // 导出配置来控制缓存行为 // 静态渲染(默认) export const dynamic = 'force-static'; // 动态渲染 export const dynamic = 'force-dynamic'; // 自动(根据请求决定) export const dynamic = 'auto'; // 重新验证间隔 export const revalidate = 3600; // 1 小时 export default async function ProductsPage() { const products = await fetch('https://api.example.com/products').then(res => res.json()); return <ProductList products={products} />; }

3.3 缓存数据去重

// 相同 URL 的请求会自动去重 async function Dashboard() { // 这两个请求相同,只会发起一次 const user = await fetch('https://api.example.com/user').then(res => res.json()); const userProfile = await fetch('https://api.example.com/user').then(res => res.json()); return ( <div> <h1>{user.name}</h1> <p>{userProfile.bio}</p> </div> ); }

4. 重新验证 (Revalidation)

4.1 时间重新验证

// app/blog/page.js async function BlogPage() { const posts = await fetch('https://api.example.com/posts', { next: { revalidate: 60 }, // 每分钟重新验证 }).then(res => res.json()); return <PostList posts={posts} />; }

4.2 按需重新验证

// app/api/revalidate/route.js import { revalidateTag, revalidatePath } from 'next/cache'; import { NextResponse } from 'next/server'; export async function POST(request) { const { tag, path } = await request.json(); // 按标签重新验证 if (tag) { revalidateTag(tag); } // 按路径重新验证 if (path) { revalidatePath(path); } return NextResponse.json({ revalidated: true }); }
// 使用标签缓存 async function PostsPage() { const posts = await fetch('https://api.example.com/posts', { next: { tags: ['posts', 'blog'] }, // 添加标签 }).then(res => res.json()); return <PostList posts={posts} />; } // 在其他地方触发重新验证 await fetch('/api/revalidate', { method: 'POST', body: JSON.stringify({ tag: 'posts' }), });

4.3 路由处理器中的重新验证

// app/api/posts/route.js import { revalidatePath } from 'next/cache'; import { NextResponse } from 'next/server'; export async function POST(request) { const post = await request.json(); // 保存到数据库 await db.post.create({ data: post }); // 重新验证博客页面 revalidatePath('/blog'); return NextResponse.json(post); }

5. 高级数据获取模式

5.1 并行数据获取

// app/dashboard/page.js async function DashboardPage() { // 并行获取多个数据 const [user, posts, notifications] = await Promise.all([ fetch('https://api.example.com/user').then(res => res.json()), fetch('https://api.example.com/posts').then(res => res.json()), fetch('https://api.example.com/notifications').then(res => res.json()), ]); return ( <div> <UserCard user={user} /> <PostList posts={posts} /> <NotificationList notifications={notifications} /> </div> ); }

5.2 串行数据获取

// app/user/[id]/page.js async function UserProfilePage({ params }) { const { id } = await params; // 先获取用户 const user = await fetch(`https://api.example.com/users/${id}`).then(res => res.json()); // 然后获取用户的文章 const posts = await fetch(`https://api.example.com/users/${user.id}/posts`).then(res => res.json()); return ( <div> <h1>{user.name}</h1> <PostList posts={posts} /> </div> ); }

5.3 条件数据获取

// app/search/page.js async function SearchPage({ searchParams }) { const { q, category } = await searchParams; // 只有有关键词时才获取数据 if (!q) { return <div>请输入搜索关键词</div>; } const results = await fetch( `https://api.example.com/search?q=${q}&category=${category || ''}` ).then(res => res.json()); return <SearchResults results={results} query={q} />; }

6. 错误处理

6.1 基础错误处理

// app/products/page.js async function ProductsPage() { try { const products = await fetch('https://api.example.com/products').then(res => { if (!res.ok) throw new Error('获取失败'); return res.json(); }); return <ProductList products={products} />; } catch (error) { return <div>加载失败: {error.message}</div>; } }

6.2 使用 error.js

// app/products/error.js 'use client'; export default function Error({ error, reset }) { return ( <div> <h2>出错了</h2> <p>{error.message}</p> <button onClick={reset}>重试</button> </div> ); }

6.3 使用 not-found

// app/products/[id]/page.js import { notFound } from 'next/navigation'; async function ProductPage({ params }) { const { id } = await params; const product = await fetch(`https://api.example.com/products/${id}`).then(res => { if (!res.ok) return null; return res.json(); }); if (!product) { notFound(); } return <ProductDetail product={product} />; }

7. 完整示例:博客系统

// app/blog/page.js import Link from 'next/link'; import { Suspense } from 'react'; async function getPosts() { const res = await fetch('https://jsonplaceholder.typicode.com/posts', { next: { revalidate: 3600, tags: ['posts'] }, }); if (!res.ok) throw new Error('获取文章失败'); return res.json(); } async function getCategories() { const res = await fetch('https://api.example.com/categories', { cache: 'force-cache', }); return res.json(); } async function getLatestPosts() { const res = await fetch('https://api.example.com/posts/latest', { cache: 'no-store', }); return res.json(); } // 文章列表组件 async function PostList() { const posts = await getPosts(); return ( <div className="posts-grid"> {posts.map(post => ( <article key={post.id} className="post-card"> <h2> <Link href={`/blog/${post.id}`}>{post.title}</Link> </h2> <p>{post.body.substring(0, 100)}...</p> </article> ))} </div> ); } // 侧边栏组件 async function Sidebar() { const categories = await getCategories(); return ( <aside className="sidebar"> <h3>分类</h3> <ul> {categories.map(cat => ( <li key={cat.id}> <Link href={`/category/${cat.slug}`}>{cat.name}</Link> </li> ))} </ul> </aside> ); } // 最新文章组件 async function LatestPosts() { const posts = await getLatestPosts(); return ( <div className="latest-posts"> <h3>最新文章</h3> <ul> {posts.map(post => ( <li key={post.id}> <Link href={`/blog/${post.id}`}>{post.title}</Link> </li> ))} </ul> </div> ); } // 主页 export default async function BlogHomePage() { return ( <div className="blog-container"> <div className="main-content"> <h1>博客文章</h1> <Suspense fallback={<div>加载文章...</div>}> <PostList /> </Suspense> </div> <div className="sidebar"> <Suspense fallback={<div>加载分类...</div>}> <Sidebar /> </Suspense> <Suspense fallback={<div>加载最新文章...</div>}> <LatestPosts /> </Suspense> </div> </div> ); } // app/blog/[id]/page.js import { notFound } from 'next/navigation'; import { Suspense } from 'react'; async function getPost(id) { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, { next: { revalidate: 60 }, }); if (!res.ok) return null; return res.json(); } async function getComments(postId) { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`, { cache: 'no-store', // 评论实时获取 }); return res.json(); } async function PostContent({ id }) { const post = await getPost(id); if (!post) { notFound(); } return ( <article> <h1>{post.title}</h1> <div className="post-content">{post.body}</div> </article> ); } async function Comments({ postId }) { const comments = await getComments(postId); return ( <div className="comments"> <h3>评论 ({comments.length})</h3> {comments.map(comment => ( <div key={comment.id} className="comment"> <strong>{comment.name}</strong> <p>{comment.body}</p> </div> ))} </div> ); } export default async function BlogPostPage({ params }) { const { id } = await params; return ( <div className="post-container"> <Suspense fallback={<div>加载文章...</div>}> <PostContent id={id} /> </Suspense> <Suspense fallback={<div>加载评论...</div>}> <Comments postId={id} /> </Suspense> </div> ); } // app/api/revalidate/route.js import { revalidateTag, revalidatePath } from 'next/cache'; import { NextResponse } from 'next/server'; export async function POST(request) { const { tag, path } = await request.json(); if (tag) { revalidateTag(tag); } if (path) { revalidatePath(path); } return NextResponse.json({ revalidated: true }); }

8. 总结

核心要点

要点说明
Server Component直接 async/await 获取数据
缓存策略force-cache、no-store、revalidate
重新验证时间间隔、按需(tag/path)
最佳实践并行获取、Suspense 流式渲染

缓存策略选择

数据类型推荐策略示例
静态内容force-cache产品说明、帮助文档
动态内容no-store实时数据、用户状态
准实时内容revalidate新闻、博客、商品列表

记忆口诀

数据获取用 fetch,缓存策略三个选
静态缓存 force-cache,动态数据 no-store
ISR 定时 revalidate,按需重验证用 tag


9. 相关资源

  • Next.js 数据获取
  • Next.js 缓存
  • fetch API 文档

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询