使用Prisma实现cursor-based pagination时出现TypeError的解决咨询
问题描述
在Next.js API路由中使用Prisma 4.2.1实现帖子游标分页时,传递cursor参数触发500错误,报错信息如下:
TypeError: Cannot read properties of undefined (reading 'createdAt')
at getPost (webpack-internal:///(api)/./lib/api/post.ts:67:46)
error - TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of TypeError
移除cursor相关代码后API可正常返回数据,尝试过升级Prisma至4.2.1、对cursor调用toString()、修改AllPosts接口类型等操作,均无法解决问题。
相关代码
API路由代码
import prisma from "@/lib/prisma"; import type { NextApiRequest, NextApiResponse } from "next"; import type { Post, Site } from ".prisma/client"; import type { Session } from "next-auth"; import { revalidate } from "@/lib/revalidate"; import type { WithSitePost } from "@/types"; interface AllPosts { posts: Array<Post>; site: Site | null; } export async function getPost( req: NextApiRequest, res: NextApiResponse, session: Session ): Promise<void | NextApiResponse<AllPosts | (WithSitePost | null)>> { 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("Bad request. Query parameters are not valid."); if (!session.user.id) return res.status(500).end("Server failed to get session user ID"); try { if (postId) { const post = await prisma.post.findFirst({ where: { id: postId, site: { user: { id: session.user.id, }, }, }, include: { site: true, }, }); return res.status(200).json(post); } const site = await prisma.site.findFirst({ where: { id: siteId, user: { id: session.user.id, }, }, }); const posts = !site ? [] : await prisma.post.findMany({ take: 10, skip: cursor === undefined ? 0 : 1, cursor: { id: cursor, }, where: { site: { id: siteId, }, published: JSON.parse(published || "true"), }, orderBy: { createdAt: "desc", }, }); const lastPostInResults = posts[9]; const nextCursor = lastPostInResults.createdAt; return res.status(200).json({ posts, site, nextCursor, }); } catch (error) { console.error(error); return res.status(500).end(error); } }
Prisma Schema定义
model Post { id String @id @default(cuid()) title String? @db.Text content String? @db.LongText slug String @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt published Boolean @default(false) site Site? @relation(fields: [siteId], references: [id], onDelete: Cascade) siteId String? @@unique([id, siteId], name: "post_site_constraint") } model Site { id String @id @default(cuid()) name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id]) userId String? posts Post[] }
修复方案
1. 解决lastPostInResults未定义的问题
报错直接原因:当返回的帖子数量不足10条时,posts[9]为undefined,访问其createdAt属性触发TypeError。需改为动态获取最后一条数据,并判断是否存在:
const lastPostInResults = posts[posts.length - 1]; // 用唯一id作为游标,避免createdAt重复导致分页异常 const nextCursor = lastPostInResults ? lastPostInResults.id : null;
2. 对齐游标字段与分页逻辑
Prisma要求游标分页的cursor字段需与查询逻辑匹配:
- 当前代码用
id作为游标,但orderBy是createdAt,需确保cursor参数是有效的Post ID字符串 - 若要基于
createdAt分页,需将cursor转换为Date类型,同时保证createdAt的唯一性(不推荐,可能存在多条数据时间相同的情况)
推荐使用id作为游标(唯一且稳定),修改后的findMany查询:
const posts = !site ? [] : await prisma.post.findMany({ take: 10, skip: cursor ? 1 : 0, // 仅当cursor存在时传入游标参数 cursor: cursor ? { id: cursor } : undefined, where: { site: { id: siteId }, published: JSON.parse(published || "true"), }, orderBy: { createdAt: "desc" }, });
3. 优化错误处理
res.end()仅接受字符串、Buffer等类型,直接传入Error对象会触发第二个报错,需转换为字符串:
catch (error) { console.error(error); const errorMsg = error instanceof Error ? error.message : "Unknown server error"; return res.status(500).end(errorMsg); }
4. 增加cursor参数校验
在使用cursor前验证其有效性:
if (cursor && typeof cursor !== "string") { return res.status(400).end("Invalid cursor parameter: must be a string"); }
内容的提问来源于stack exchange,提问作者Luke

