Next.js 13 动态API请求分页数据的最佳实践及问题解决
Next.js App Router 分页获取数据最佳实践
问题根源
你的代码存在两个核心问题:
- 异步的
Page组件默认是Server Component,但你在其中使用了useState这类客户端Hook——Server Component不允许使用客户端状态Hook,会直接引发错误。 ArticleList作为Server Component,无法接收客户端动态状态(比如currentPage)作为props触发重新获取数据。Server Component在服务端渲染完成后,客户端状态变化无法触发它重新执行服务端的请求逻辑,这就是你遇到无限循环或数据不更新的原因。
方案一:客户端数据获取(推荐,组件内分页)
使用SWR/React Query这类客户端数据获取库,结合客户端状态实现组件内分页,同时享受缓存、自动重新请求的特性。
步骤与代码示例
- 安装SWR:
npm install swr
- 拆分组件,将客户端状态与数据获取逻辑放到Client Component中:
page.tsx(Server Component,仅做页面骨架渲染)
export default function Page() { return ( <> <h3>Articles</h3> <ArticlePagination /> </> ) }
ArticlePagination.tsx(Client Component,添加'use client'指令)
'use client'; import { useState } from 'react'; import useSWR from 'swr'; // 封装文章请求函数 const fetchArticles = async (page: number) => { const baseUrl = process.env.NODE_ENV === "development" ? SITE.URLS.TEST : SITE.URLS.LIVE; const res = await fetch(`${baseUrl}/api/articles?page=${page}`, { next: { revalidate: 5 }, }); if (!res.ok) throw new Error('获取文章失败'); return res.json(); }; export default function ArticlePagination() { const [currentPage, setCurrentPage] = useState(0); // 用SWR监听页码变化,自动重新请求 const { data, error, isLoading } = useSWR([currentPage], ([page]) => fetchArticles(page)); if (isLoading) return <div>加载中...</div>; if (error) return <div>加载文章失败</div>; return ( <> <div> {data?.articles.map((article: any) => ( <div key={article.id}>{article.id}</div> ))} </div> <button onClick={() => setCurrentPage(prev => Math.max(0, prev - 1))} disabled={currentPage === 0} > 上一页 </button> <p>当前页码:{currentPage}</p> <button onClick={() => setCurrentPage(prev => prev + 1)}>下一页</button> </> ); }
方案二:动态路由分页(URL同步状态)
如果需要将分页状态同步到URL(比如支持分享带页码的链接),可以用动态路由实现页面级分页,Server Component根据路由参数获取对应页码的数据。
步骤与代码示例
- 创建动态路由文件
app/articles/[page]/page.tsx:
import Link from 'next/link'; type Props = { params: { page: string }; }; export default async function ArticlePage({ params }: Props) { const page = parseInt(params.page) || 0; const baseUrl = process.env.NODE_ENV === "development" ? SITE.URLS.TEST : SITE.URLS.LIVE; const res = await fetch(`${baseUrl}/api/articles?page=${page}`, { next: { revalidate: 5 }, }); if (!res.ok) throw new Error('获取文章失败'); const data = await res.json(); return ( <> <h3>Articles</h3> <div> {data.articles.map((article: any) => ( <div key={article.id}>{article.id}</div> ))} </div> <Link href={`/articles/${Math.max(0, page - 1)}`}> <button disabled={page === 0}>上一页</button> </Link> <p>当前页码:{page}</p> <Link href={`/articles/${page + 1}`}> <button>下一页</button> </Link> </> ); }
- 首页添加跳转入口(
app/page.tsx):
import Link from 'next/link'; export default function Home() { return ( <div> <h1>首页</h1> <Link href="/articles/0">查看文章列表</Link> </div> ); }
内容的提问来源于stack exchange,提问作者douglasrcjames
相关产品推荐
相关产品推荐

