如何在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
相关产品推荐
相关产品推荐

