NextAuth PrismaAdapter的 linkAccount() 在Vercel上无限期地挂起,但在本地可以正常工作
问题
我在使用带有 @auth/prisma-adapter的 OAuth验证的NextAuth v4.24.13。登录流程在本地开发环境中运行正常,但在Vercel生产环境的账户关联步骤上,总是卡住并超时。
发生了什么:
- 用户点击“Sign in with Google”
- Google OAuth成功完成
- /api/auth/callback/google端点卡住
- 数据库中创建了用户记录(约240ms)
- 调用linkAccount() 适配器方法来创建Account记录
- Account INSERT从不完成——一直卡住直到超时(5-10秒)
- events.linkAccount事件从未触发(证实了适配器方法未完成)
环境:
- Next.js 15.1.6(App Router)
- NextAuth 4.24.13
- @auth/prisma-adapter 2.7.4
- Prisma采用自定义生成的客户端
- @prisma/adapter-pg 6.1.0
- PostgreSQL(自托管,本地和Vercel都可访问)
- 部署在Vercel(无服务器函数)
Prisma Client设置 (src/lib/prisma.ts):
import { PrismaClient } from "@/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
adapter: new PrismaPg(
new Pool({ connectionString: process.env.DATABASE_URL })
),
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
NextAuth配置 (src/lib/auth.ts):
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "./prisma";
const baseAdapter = PrismaAdapter(prisma);
export const authOptions: NextAuthOptions = {
adapter: {
...baseAdapter,
// Added 10s timeout wrapper to debug (instead of waiting for full Vercel 300s timeout)
linkAccount: async (account: any) => {
console.log('[linkAccount adapter] Called with account:', account);
const start = Date.now();
try {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('linkAccount timeout after 10s')), 10000)
);
// This hangs - never completes
const createPromise = prisma.account.create({
data: account
});
const result = await Promise.race([
createPromise,
timeoutPromise
]);
console.log(`[linkAccount adapter] Completed in ${Date.now() - start}ms`);
return result;
} catch (error) {
console.error('[linkAccount adapter] Error:', error);
console.log(`[linkAccount adapter] Failed after ${Date.now() - start}ms`);
throw error;
}
},
} as Adapter,
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true,
}),
],
session: { strategy: "jwt" },
};
我已验证的事项
网络连通性并非问题:
- tcpdump证实Vercel能成功连接到PostgreSQL
- TCP握手完成,数据在交换
- 同一端点的其他数据库操作(创建用户、SELECT查询)正常工作
Vercel日志显示:
[linkAccount adapter] Called with account: { userId: '....', ... }
[linkAccount adapter] Error: Error: linkAccount timeout after 5s
[linkAccount adapter] Failed after 10006ms
数据库日志显示:
- User INSERT查询成功执行
- Account INSERT查询在PostgreSQL日志中从未出现
- 无锁定、无长时间运行的查询
- pg_stat_activity显示无挂起连接
浏览器显示:
- /api/auth/callback/google端点处于“pending”状态约10秒
- 之后显示NextAuth错误页
我尝试过的方案
- ✅ 添加了带明确超时设置的自定义连接池 - 无变化
- ✅ 使用 $executeRaw的原始SQL代替prisma.account.create() - 仍然卡住
- ✅ 将连接池大小从1 增加到10个连接 - 无变化
- ✅ 验证了iptables/防火墙规则 - tcpdump显示连接成功
- ✅ 将Prisma配置简化为官方的单例模式 - 仍然卡住
- ✅ 移除了适配器覆盖 - 基础PrismaAdapter也会卡住
问题
- 为什么linkAccount() 只在Vercel的无服务器环境中卡住,而在本地可以工作?
- 是否存在 @prisma/adapter-pg + pg Pool +无服务器函数的已知问题?
- 我应该在Vercel上使用Prisma Accelerate还是像PgBouncer这样的外部连接池工具?
- pg Pool是否与Vercel的无服务器冷启动不兼容?
根据数据库日志,这个查询甚至没有到达PostgreSQL,这表明问题出在Prisma/适配器层,而不是数据库本身。
附注: Claude代码不能解决它。需要休息一下,或许明天再尝试排查。
更新
睡眠确实重要。
我做了哪些事:
- 将本地应用连接到生产环境,能够写入Accounts表(本地端口)
- 尝试在生产环境登录,登录在Vercel上可用
- 在我的应用中更新个人信息(在Vercel上)
这意味着:
- ✅ Vercel能写入数据库(会话工作正常)
- ✅ Vercel能通过API路由手动写入Account表
- ✅ 本地开发 → 生产数据库的所有操作均可执行,包括linkAccount
- ❌ OAuth回调阶段只有
linkAccount会在Vercel的无服务器环境中挂起
解决方案
答案:这是一个WireGuard MTU/MSS不匹配的问题
经过大量调试,我发现这并不是Prisma或 NextAuth的问题——这是网络层面的问题,出在我的WireGuard VPN隧道上。严格来说,这个问题从一开始就算不上有答案。我的基础设施相当独特。Prisma、Next.js、Vercel都没有问题。完全是我的错 😅。
我的诊断过程
我使用tcpdump监控数据库流量,发现TCP重传:
tcpdump -i ens18 'tcp port 5432' -n
我看到的情况:
- Vercel成功连接并交换初始握手数据
- 在某个时刻,数据库发送了一个SACK(选择性确认),指示丢失了一个数据包(1398字节)
- Vercel不断重新传输同一个数据包
- PostgreSQL日志显示没有接收到查询——数据包从未达到应用层
根本原因:MTU不匹配
我的基础设施设置:
- Vercel无服务器 → WireGuard VPN → 自托管PostgreSQL
- 外部接口(ens18):MTU 1500字节
- WireGuard隧道(wg0):MTU 1420字节(80字节开销)
问题所在:
- OAuth令牌INSERT:1398字节数据+ 40字节TCP/IP头,总计1438字节
- 1438 > 1420 → WireGuard静默丢弃超出MTU的数据包
为什么OAuth失败但其他操作正常工作
- 小型操作(用户资料更新、API密钥):< 500字节 → ✅ 适合MTU
- OAuth登录:大型JWT令牌(id_token + access_token + refresh_token)= > 1400字节 → ❌ 超出MTU
解决方案:TCP MSS限幅
为PostgreSQL(端口5432)添加iptables规则,强制TCP最大报文段大小(MSS)保持在WireGuard MTU之内:
iptables -t mangle -A FORWARD -p tcp --dport 5432 -o wg0 -j TCPMSS --set-mss 1360
iptables -t mangle -A FORWARD -p tcp --sport 5432 -o ens18 -j TCPMSS --set-mss 1360
为什么是1360?
- WireGuard MTU:1420
- TCP/IP头:40字节
- 安全MSS:1420 - 40 = 1380(为了留出边距,取1360)
附注:睡眠真的很重要。😅
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。