prisma: Using Prisma at the edge seems to cause React Server Components to randomly return 500s during prefetching. (probably Vercel Middleware problem with `next-auth`)
Bug description
Hello,
I’ve setup a prisma data proxy at https://cloud.prisma.io/ and I’m now using prisma at the edge in my next.js. I use the middleware and next-auth to protect my pages, routes, etc.
Problem is that, when deployed, on every page refresh a few React Server Components are supposed to be prefetched but some of them will return an error 500. Error is INTERNAL_EDGE_FUNCTION_UNHANDLED_ERROR
.
If I remove the prisma adapter from next-auth it works fine.
How to reproduce
- Ensure to test with a Next.js app that has a few links (using the Link component) pointing to a few pages.
- Create and configure your Data proxy at https://cloud.prisma.io/
- Generate and use the prisma client at the edge
import { PrismaClient } from "@prisma/client/edge"; declare global { // eslint-disable-next-line no-var var cachedPrisma: PrismaClient; } let prisma: PrismaClient; if (process.env.NODE_ENV === "production") { prisma = new PrismaClient(); } else { if (!global.cachedPrisma) { global.cachedPrisma = new PrismaClient(); } prisma = global.cachedPrisma; } export const db = prisma; export { type PrismaClient };
- Use the next-auth prisma adapter to use the “database” session strategy for your next-auth setup.
import NextAuth, { type DefaultSession } from 'next-auth' import GitHub from 'next-auth/providers/github' import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { db } from "./db"; declare module 'next-auth' { interface Session { user: { /** The user's id. */ id: string } & DefaultSession['user'] } } export const { handlers: { GET, POST }, auth, CSRF_experimental // will be removed in future } = NextAuth({ adapter: PrismaAdapter(db), providers: [GitHub], callbacks: { jwt({ token, profile }) { if (profile) { token.id = profile.id token.image = profile.picture } return token }, authorized({ auth }) { return !!auth?.user // this ensures there is a logged in user for -every- request } }, pages: { signIn: '/sign-in' // overrides the next-auth default signin page https://authjs.dev/guides/basics/pages } })
- Ensure that your pages are “protected” via the next.js middleware.
export { auth as middleware } from './auth' export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] }
- Go to your deployed site and observe the RSC randomly throwing 500s on each page refresh. The problem doesn’t happen during local development.
- Switch back to JWT session strategy by commenting out the adapter to not use it.
- Redeploy and see how everything is now working fine.
Expected behavior
React Server Components should be prefetched without any problems
Prisma information
// Add your schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
extensions = [vector]
}
model Account {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
name String?
email String? @unique
username String? @unique
emailVerified DateTime?
image String?
phone String?
language String?
country String?
accounts Account[]
sessions Session[]
posts Post[]
comments Comment[]
projects ProjectUser[]
workspaces WorkspaceUser[]
subscription Subscription[]
stripeCustomerId String? @unique
theme Theme? @relation(fields: [themeId, themeColor], references: [id, color])
themeId String?
themeColor String?
}
model VectorDocument {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
public Boolean
embedding Unsupported("vector(1536)")?
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
model Post {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
published Boolean @default(false)
title String
slug String @unique
image String?
excerpt String? @db.Text
content String? @db.Text
comments Comment[]
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId String
category Category @relation(fields: [categoryId], references: [id])
categoryId String
// views Int @default(0)
// likes Int @default(0)
// shares Int @default(0)
}
model Comment {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
content String @db.Text
author User @relation(fields: [authorId], references: [id])
authorId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
postId String
}
model Category {
id String @id @default(cuid())
slug String @unique
name String
posts Post[]
}
model ProjectUser {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId String
@@id([userId, projectId])
}
model Project {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
users ProjectUser[]
posts Post[]
name String?
description String? @db.Text
slug String? @unique
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
}
enum WorkspaceRole {
USER
MANAGER
ADMIN
}
model WorkspaceUser {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
workspaceRole WorkspaceRole @default(USER)
@@id([userId, workspaceId])
}
model Workspace {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
projects Project[]
users WorkspaceUser[]
name String?
description String? @db.Text
slug String? @unique
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
subscriptionId String?
}
model Subscription {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
user User @relation(fields: [stripeCustomerId], references: [stripeCustomerId])
stripeCustomerId String
stripeSubscriptionId String? @unique
stripePriceId String?
stripeCurrentPeriodEnd DateTime?
workspaces Workspace[]
}
model Theme {
id String @unique
color String
users User[]
@@unique([id, color])
}
Environment & setup
- OS: macOS
- Database: PostgreSQL
- Node.js version: node 18 (on Vercel)
Prisma Version
prisma : 5.1.0
@prisma/client : 5.1.0
Current platform : darwin-arm64
Query Engine (Node-API) : libquery-engine a9b7003df90aa623086e4d6f4e43c72468e6339b (at node_modules/.pnpm/registry.npmjs.org+@prisma+engines@5.1.0/node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node)
Schema Engine : schema-engine-cli a9b7003df90aa623086e4d6f4e43c72468e6339b (at node_modules/.pnpm/registry.npmjs.org+@prisma+engines@5.1.0/node_modules/@prisma/engines/schema-engine-darwin-arm64)
Schema Wasm : @prisma/prisma-schema-wasm 5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b
Default Engines Hash : a9b7003df90aa623086e4d6f4e43c72468e6339b
Studio : 0.492.0
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 1
- Comments: 22 (11 by maintainers)
Sorry, let me clarify:
We are not using next-auth at all. we are using auth0 and their nextjs SDK.
We have a
middleware.ts
edge function that reads the auth0 session data and compares that with data in prisma, fetched via Data Proxy.the middleware matcher is as follows:
my understanding is that this wouldn’t match the
__next
routes that power prefetching but I’m pretty ignorant of how server components work, to be honest.The result is, occasionally, a burst of prefetch requests that hang and then fail with 500s, with error codes that indicate an edge function failed - AFAIK the only edge function we use is the middleware.
Data Proxy user: gablabelle Data Proxy project: prisma-edge-errors
Thanks for the heads up!
When specifying the connection string in Data Proxy, I have removed the
-pooler
suffix and removed thepgbouncer=true
query param from the URL but I have kept theconnect_timeout=10
in there.Done, deployed.
@janpio I’m a bit reassured that you were able to reproduce the issue lol. When testing again a few minutes ago, I see that the error seems to be less frequent, but it still happens though.
FYI, I’m using a Neon serverless database. That’s why I use the
shadowDatabaseUrl
.Thanks for your quick responses, I’ll be waiting for your update on this and I’ll be using the JWT session strategy in the meantime.
Hello @janpio,
I have setup the requested minimal reproduction project and it’s available on Github here: https://github.com/gablabelle/prisma-edge-errors
I have also deployed that app to https://prisma-edge-errors.vercel.app/. After logging in you’ll be able to observe the errors in the dev tools’ Network tab. Upon each page refresh random pages will sometimes return 500s. Disable cache before refreshing the page.
The error I get in the Vercel logs is the following for each page prefetching that failed with a 500: