Next.js 提供了多种数据获取方法,开发者可以根据不同的渲染策略和需求选择合适的方式。以下是 Next.js 中主要的数据获取方法:
Pages Router 数据获取方法
1. getStaticProps
在构建时获取数据,用于静态生成(SSG)。
javascriptexport async function getStaticProps(context) { const data = await fetch('https://api.example.com/data').then(r => r.json()); return { props: { data }, revalidate: 60, // 可选:ISR,每 60 秒重新生成 notFound: false, // 可选:返回 404 页面 redirect: { destination: '/login', permanent: false }, // 可选:重定向 }; } export default function Page({ data }) { return <div>{data.content}</div>; }
适用场景:
- 数据在构建时可用
- 页面内容不经常变化
- 需要预渲染以提升 SEO
2. getServerSideProps
在每次请求时获取数据,用于服务器端渲染(SSR)。
javascriptexport async function getServerSideProps(context) { const { req, res, query, params } = context; // 可以访问请求和响应对象 const token = req.cookies.token; const data = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${token}` } }).then(r => r.json()); return { props: { data }, // 不支持 revalidate }; } export default function Page({ data }) { return <div>{data.content}</div>; }
适用场景:
- 数据在请求时才能获取
- 需要访问请求/响应对象
- 内容频繁变化
3. getStaticPaths
用于动态路由的静态生成,定义所有可能的路径。
javascriptexport async function getStaticPaths() { const posts = await getAllPosts(); return { paths: posts.map(post => ({ params: { slug: post.slug } })), fallback: false, // 或 'blocking' 或 true }; } export async function getStaticProps({ params }) { const post = await getPostBySlug(params.slug); return { props: { post }, }; } export default function BlogPost({ post }) { return <div>{post.title}</div>; }
fallback 选项:
false:只返回预生成的路径,其他路径返回 404'blocking':服务器渲染新路径,等待完成后返回true:立即返回静态页面,后台生成新路径
App Router 数据获取方法
1. 服务器组件中的 fetch
在服务器组件中直接使用 fetch 获取数据。
javascriptasync function Page() { const data = await fetch('https://api.example.com/data', { cache: 'force-cache', // 或 'no-store', 'no-cache', 'default' next: { revalidate: 60, // ISR tags: ['data'] // 用于按需重新验证 } }).then(r => r.json()); return <div>{data.content}</div>; }
cache 选项:
force-cache:强制使用缓存(默认)no-store:不使用缓存no-cache:每次验证缓存default:使用默认缓存策略
2. 使用 React Server Components
javascriptasync function BlogList() { const posts = await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } }).then(r => r.json()); return ( <div> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); }
3. 使用 Suspense 和 Streaming
javascriptimport { Suspense } from 'react'; async function SlowComponent() { const data = await fetch('https://api.example.com/slow', { next: { revalidate: 60 } }).then(r => r.json()); return <div>{data.content}</div>; } export default function Page() { return ( <div> <h1>Page Title</h1> <Suspense fallback={<div>Loading...</div>}> <SlowComponent /> </Suspense> </div> ); }
客户端数据获取
1. 使用 useEffect
javascript'use client'; import { useState, useEffect } from 'react'; export default function ClientDataComponent() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(data => { setData(data); setLoading(false); }); }, []); if (loading) return <div>Loading...</div>; return <div>{data.content}</div>; }
2. 使用 SWR
javascript'use client'; import useSWR from 'swr'; const fetcher = (url) => fetch(url).then(res => res.json()); export default function SWRComponent() { const { data, error, isLoading } = useSWR('/api/data', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, dedupingInterval: 60000, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error</div>; return <div>{data.content}</div>; }
3. 使用 React Query
javascript'use client'; import { useQuery } from '@tanstack/react-query'; async function fetchData() { const res = await fetch('/api/data'); return res.json(); } export default function ReactQueryComponent() { const { data, error, isLoading } = useQuery({ queryKey: ['data'], queryFn: fetchData, staleTime: 60000, cacheTime: 300000, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error</div>; return <div>{data.content}</div>; }
数据获取最佳实践
1. 选择合适的方法
| 场景 | 推荐方法 |
|---|---|
| 静态内容,构建时可用 | getStaticProps / SSG |
| 动态内容,需要实时数据 | getServerSideProps / SSR |
| 需要用户交互 | 客户端数据获取 |
| SEO 重要,内容变化不频繁 | SSG + ISR |
| 需要访问请求/响应对象 | getServerSideProps |
2. 缓存策略
javascript// 长期缓存 fetch('/api/data', { cache: 'force-cache', next: { revalidate: 3600 } }); // 短期缓存 fetch('/api/data', { cache: 'no-store' }); // 按需重新验证 fetch('/api/data', { next: { tags: ['data'] } }); // 在 API 路由中重新验证 import { revalidateTag } from 'next/cache'; export async function POST() { revalidateTag('data'); return Response.json({ revalidated: true }); }
3. 错误处理
javascriptexport async function getStaticProps() { try { const data = await fetchData(); return { props: { data } }; } catch (error) { return { notFound: true, }; } }
4. 加载状态
javascript// App Router - 使用 loading.js // app/loading.js export default function Loading() { return <div>Loading...</div>; } // Pages Router - 使用自定义加载组件 export default function LoadingPage() { return <div>Loading...</div>; }
5. 并行数据获取
javascript// 并行获取多个数据 export async function getStaticProps() { const [posts, users, comments] = await Promise.all([ fetch('/api/posts').then(r => r.json()), fetch('/api/users').then(r => r.json()), fetch('/api/comments').then(r => r.json()), ]); return { props: { posts, users, comments }, }; }
性能优化建议
- 使用 ISR:对于需要定期更新的内容,使用 ISR 而不是 SSR
- 缓存数据:合理设置缓存时间,减少不必要的请求
- 并行获取:使用 Promise.all 并行获取多个数据源
- 流式渲染:使用 Suspense 实现流式渲染,提升用户体验
- 客户端缓存:使用 SWR 或 React Query 缓存客户端数据
- 按需重新验证:使用标签系统按需重新验证数据
通过合理选择和使用这些数据获取方法,可以构建高性能、用户体验良好的 Next.js 应用。