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

使用Prisma实现cursor-based pagination时出现TypeError的解决咨询

Next.js + Prisma游标分页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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:36:23