You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13 动态API请求分页数据的最佳实践及问题解决

Next.js App Router 分页获取数据最佳实践

问题根源

你的代码存在两个核心问题:

  1. 异步的Page组件默认是Server Component,但你在其中使用了useState这类客户端Hook——Server Component不允许使用客户端状态Hook,会直接引发错误。
  2. ArticleList作为Server Component,无法接收客户端动态状态(比如currentPage)作为props触发重新获取数据。Server Component在服务端渲染完成后,客户端状态变化无法触发它重新执行服务端的请求逻辑,这就是你遇到无限循环或数据不更新的原因。

方案一:客户端数据获取(推荐,组件内分页)

使用SWR/React Query这类客户端数据获取库,结合客户端状态实现组件内分页,同时享受缓存、自动重新请求的特性。

步骤与代码示例

  1. 安装SWR:
npm install swr
  1. 拆分组件,将客户端状态与数据获取逻辑放到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根据路由参数获取对应页码的数据。

步骤与代码示例

  1. 创建动态路由文件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>
    </>
  );
}
  1. 首页添加跳转入口(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 02:33:12