关于 nextjs-data-fetching
面向使用 Next.js App Router 的开发者的数据获取技能,涵盖服务端与客户端获取、缓存、重新验证、加载与错误状态,以及 Server Actions。
主要功能
- 01在 Server Components 中获取数据
- 02并行执行独立请求
- 03配置缓存与重新验证
- 04集成 SWR 或 React Query
- 05处理加载与错误状态
- 06使用 Server Actions 执行变更
适用场景
- 01构建按计划刷新的数据页面
- 02并发加载仪表盘数据
- 03添加客户端缓存与更新
- 04通过 Server Actions 提交表单并重新验证缓存
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill nextjs-data-fetching安装后在 Claude Code 中输入/nextjs-data-fetching确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/caching-strategies.mdclient-fetching.mddata-fetching.mderror-boundaries.mderror-loading-states.mdreact-query.mdserver-actions.md详细介绍
Next.js 数据获取
概述
介绍 Next.js App Router 中的数据获取模式:服务端获取、SWR/React Query 集成、ISR、重新验证、错误边界和加载状态。
适用场景
- 在 Next.js App Router 中实现数据获取
- 在 Server Components 和 Client Components 之间进行选择
- 设置 SWR 或 React Query,以实现客户端缓存
- 配置 ISR、基于时间的重新验证或按需重新验证
- 处理加载和错误状态
- 使用 Server Actions 构建表单
说明
Server Component 中的数据获取
在异步 Server Components 中直接获取数据:
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
并行数据获取
对互不依赖的请求使用 Promise.all():
async function getDashboardData() {
const [user, posts, analytics] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/analytics').then(r => r.json()),
]);
return { user, posts, analytics };
}
export default async function DashboardPage() {
const { user, posts, analytics } = await getDashboardData();
// Render dashboard
}
顺序数据获取(存在依赖时)
async function getUserPosts(userId: string) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
基于时间的重新验证(ISR)
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // Revalidate every 60 seconds
});
return res.json();
}
按需重新验证
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag');
if (tag) {
revalidateTag(tag);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false }, { status: 400 });
}
为数据添加标签,以便选择性地重新验证:
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'], revalidate: 3600 }
});
return res.json();
}
退出缓存
async function getRealTimeData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return res.json();
}
// Or:
export const dynamic = 'force-dynamic';
客户端数据获取
集成 SWR
安装:npm install swr
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function Posts() {
const { data, error, isLoading } = useSWR('/api/posts', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Failed to load posts</div>;
return (
<ul>
{data.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
集成 React Query
安装:npm install @tanstack/react-query
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
有关变更、乐观更新、无限查询和高级模式,请参阅 react-query.md。
错误边界
使用错误边界包装客户端数据获取,以妥善处理失败情况:
有关完整的 ErrorBoundary 实现(基础版和带重置回调的版本)以及数据获取用法示例,请参阅 error-boundaries.md。
Server Actions
使用 Server Actions 执行变更并重新验证缓存:
有关完整示例(包括使用 useActionState 进行表单验证、错误处理和缓存失效),请参阅 server-actions.md。
加载状态
loading.tsx 模式
// app/posts/loading.tsx
export default function PostsLoading() {
return (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-16 bg-gray-200 animate-pulse rounded" />
))}
</div>
);
}
Suspense 边界
// app/posts/page.tsx
import { Suspense } from 'react';
import { PostsList } from './PostsList';
import { PostsSkeleton } from './PostsSkeleton';
export default function PostsPage() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostsList />
</Suspense>
</div>
);
}
最佳实践
- 默认使用 Server Components — 在 Server Components 中获取数据,以提升性能
- 使用并行获取 — 对互不依赖的请求使用
Promise.all(),以降低延迟 - 选择合适的缓存策略:
- 静态数据:使用较长的重新验证间隔
- 动态数据:使用较短的重新验证间隔,或设置
cache: 'no-store' - 用户专属数据:使用动态渲染
- 妥善处理错误 — 使用错误边界包装客户端数据获取
- 实现加载状态 — 使用
loading.tsx或 Suspense 边界 - 优先在以下场景使用 SWR/React Query:实时数据、用户交互、后台更新
- 在以下场景使用 Server Actions:表单提交、需要重新验证缓存的变更
限制与警告
关键限制
- Server Components 不能使用 hooks(
useState、useEffect)或客户端数据获取库 - Client Components 必须包含
'use client'指令 - Next.js 中的
fetchAPI 在标准 Web fetch 的基础上扩展了 Next.js 专属缓存选项 - Server Actions 需要
'use server',且只能从 Client Components 或表单操作中调用
常见问题
- 在循环中获取数据 — 避免在 Server Components 中顺序获取数据;请使用并行获取
- 缓存污染 — 不要对用户专属或个性化数据使用
force-cache - 内存泄漏 — 使用实时数据时,请在 Client Components 中清理订阅
- 水合不匹配 — 使用 React Query 水合时,确保服务端和客户端渲染相同的初始状态
示例
示例 1:使用 ISR 的博客
输入: 创建一个博客页面,获取文章并每小时更新一次。
// app/blog/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
输出: 页面在构建时静态生成,并每小时重新验证一次。
示例 2:并行获取数据的仪表盘
输入: 构建一个仪表盘,并行显示用户资料、统计数据和最近活动。
// app/dashboard/page.tsx
async function getDashboardData() {
const [user, stats, activity] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
fetch('/api/activity').then(r => r.json()),
]);
return { user, stats, activity };
}
export default async function DashboardPage() {
const { user, stats, activity } = await getDashboardData();
return (
<div className="dashboard">
<UserProfile user={user} />
<StatsCards stats={stats} />
<ActivityFeed activity={activity} />
</div>
);
}
输出: 三个请求同时执行,最大限度地缩短总加载时间。
评论
支持 Markdown