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

如何在Next.js API Route、Prisma中结合useSWRInfinite实现游标分页?

实现基于游标的文章分页(Next.js + Prisma + useSWRInfinite)

1. 修改API路由以支持游标分页

首先更新你的API路由,使其接收游标参数、返回下一页游标和文章总数:

export async function getPost(
  req: NextApiRequest,
  res: NextApiResponse,
  session: Session
): Promise<void | NextApiResponse<any>> {
  const { postId, siteId, published, cursor } = req.query;

  if (Array.isArray(postId) || Array.isArray(siteId) || Array.isArray(published) || Array.isArray(cursor))
    return res.status(400).end("请求参数格式错误");

  if (!session.user.id)
    return res.status(500).end("服务器无法获取用户会话ID");

  try {
    if (postId) {
      const post = await prisma.post.findFirst({
        // 原有单篇文章逻辑保持不变
        ...
      });
      return res.status(200).json(post);
    }

    const site = await prisma.site.findFirst({
      where: {
        id: siteId,
        user: { id: session.user.id },
      },
    });

    if (!site) {
      return res.status(200).json({ posts: [], site, nextCursor: null, totalCount: 0 });
    }

    // 获取文章总数
    const totalCount = await prisma.post.count({
      where: {
        site: { id: siteId },
        published: JSON.parse(published || "true"),
      },
    });

    // 分页查询:take 11条以判断是否有下一页
    const take = 10;
    const posts = await prisma.post.findMany({
      take: take + 1,
      cursor: cursor ? { id: cursor as string } : undefined,
      skip: cursor ? 1 : 0, // 跳过游标对应的文章
      where: {
        site: { id: siteId },
        published: JSON.parse(published || "true"),
      },
      orderBy: { createdAt: "desc" },
    });

    // 计算下一页游标
    const nextCursor = posts.length > take ? posts[take].id : null;
    // 只返回10条数据
    const paginatedPosts = posts.slice(0, take);

    return res.status(200).json({
      posts: paginatedPosts,
      site,
      nextCursor,
      totalCount,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).end(error as string);
  }
}

2. 更新页面组件使用useSWRInfinite

替换原有的useSWR为useSWRInfinite,实现加载更多逻辑:

import useSWRInfinite from 'swr/infinite';
import { useRouter } from 'next/router';

const PostsPage = () => {
  const router = useRouter();
  const { id: siteId } = router.query;

  // 生成请求URL的函数
  const getKey = (pageIndex: number, previousPageData: any) => {
    // 如果没有下一页数据,停止请求
    if (previousPageData && !previousPageData.nextCursor) return null;
    // 第一页不带cursor,后续页带上一页返回的nextCursor
    const cursor = previousPageData ? `&cursor=${previousPageData.nextCursor}` : '';
    return siteId ? `/api/post?siteId=${siteId}&published=true${cursor}` : null;
  };

  const { data, error, size, setSize } = useSWRInfinite<any>(getKey, fetcher, {
    onSuccess: (pages) => {
      const firstPage = pages[0];
      if (firstPage && !firstPage.site) router.push("/");
    },
  });

  // 合并所有页的文章数据
  const allPosts = data ? data.flatMap(page => page.posts) : [];
  // 获取文章总数(从第一页数据中提取)
  const totalCount = data?.[0]?.totalCount || 0;
  // 判断是否还有下一页
  const hasMore = data?.[size - 1]?.nextCursor !== null;

  if (error) return <div>加载失败</div>;
  if (!data && siteId) return <div>加载中...</div>;
  if (!siteId) return <div>无效的站点ID</div>;

  return (
    <div>
      <h2>文章列表 ({allPosts.length}/{totalCount})</h2>
      <ul>
        {allPosts.map(post => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.createdAt.toLocaleString()}</p>
          </li>
        ))}
      </ul>
      {hasMore && (
        <button onClick={() => setSize(size + 1)} disabled={!hasMore}>
          加载更多
        </button>
      )}
      {!hasMore && allPosts.length === totalCount && (
        <p>已加载全部文章</p>
      )}
    </div>
  );
};

export default PostsPage;

关键说明

  • 游标逻辑:通过记录最后一篇文章的id作为游标,每次请求传递该游标,Prisma会从游标之后开始查询
  • 下一页判断:查询时多取1条数据,如果返回数量超过每页条数,说明还有下一页,将第11条的id作为下一页游标
  • useSWRInfinite:通过getKey函数动态生成请求URL,setSize触发加载下一页,自动缓存已加载的页面数据

内容的提问来源于stack exchange,提问作者Luke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 07:24:22