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

Next.js+MongoDB在Vercel动态路由遇504网关超时问题求助

问题描述

使用MongoDB共享集群 + Vercel免费版,本地环境无超时问题,MongoDB连接采用Mongoose复用同一连接;但在Vercel上首次访问SSG生成的课程/班级动态路由时出现504超时,后续路由缓存后加载正常(最多2秒)。


相关代码

mongodb.js

if (!process.env.MONGODB_URI) {
  throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
}

const uri = process.env.MONGODB_URI
const options = {}

let client
let clientPromise

if (process.env.NODE_ENV === 'development') {
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri, options)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
} else {
  client = new MongoClient(uri, options)
  clientPromise = client.connect()
}

// SSR
export async function getServerSideProps(context: any) {
  await db()
  const { params, query, req, res } = context
  const session = await getSession({ req })
  const cookies = parseCookies(context)
  const userCookie = cookies?.user ? JSON.parse(cookies.user) : session?.user
  const email = userCookie?.email   
  const { classId, id } = params
  const clase = await getClassById(classId, id)
  const user = await updateActualCourseSS(email, id, classId)

  return {
    props: { clase, user }
  }
} 

getClassById

import connectDB from "../../../config/connectDB" 
import Course from "../../../models/courseModel"
import User from "../../../models/userModel"
import Class from "../../../models/classModel"
import bcrypt from "bcryptjs"

export async function getClassById(id, courseId) {
  try {
    const res = await Class.where('id').equals(id).populate({ 
      path: 'course', match: {id: courseId}
    }).exec()
    const courseToSend = res.filter(r => r.course != null)

    const clase = JSON.parse(JSON.stringify(courseToSend[0]))
    return clase
  } catch (err) {
    console.log(err)
  }
}

updateActualCourseSS

export async function updateActualCourseSS(email: string, 
  courseId: string, actualChapter: number) {
  try {
    console.log('connected')
    const user: any | null = await User.findOne({ email: email }).exec()
    const courseDB: CoursesDB | null = await Course.findOne({ id: courseId}).exec()

    const index = user?.courses.findIndex((course: CourseUser) => 
      course.course.valueOf() === courseDB?._id.valueOf())

    index != null && user != null ? 
      user.courses[index].actualChapter = actualChapter : null

    user.courses[index].actualTime = 0;

    await user?.save()

    return JSON.parse(JSON.stringify(user));
  } catch (err) {
    console.log(err)
  }
}

问题分析与解决方案

核心原因

Vercel免费版Serverless函数冷启动+MongoDB共享集群首次连接延迟,叠加getServerSideProps里的串行数据库操作,导致首次请求触发504超时(Vercel免费版Serverless函数超时阈值为10秒,跨区域连接+冷启动的耗时容易触达上限)。

具体优化措施

  1. 修复生产环境MongoDB连接复用逻辑
    当前生产环境每次请求都会创建新连接,改成和开发环境一致的全局连接复用:
// 修改mongodb.js生产环境代码段
if (process.env.NODE_ENV === 'development') {
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri, options)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
} else {
  // 生产环境复用全局连接(Vercel同实例生命周期内会保留全局变量)
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri, options)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
}
  1. 并行化数据库操作
    getServerSideProps中getClassById和updateActualCourseSS原本串行执行,改成并行减少总耗时:
export async function getServerSideProps(context: any) {
  await db()
  const { params, query, req, res } = context
  const session = await getSession({ req })
  const cookies = parseCookies(context)
  const userCookie = cookies?.user ? JSON.parse(cookies.user) : session?.user
  const email = userCookie?.email   
  const { classId, id } = params
  
  // 并行执行两个异步操作
  const [clase, user] = await Promise.all([
    getClassById(classId, id),
    updateActualCourseSS(email, id, classId)
  ])

  return {
    props: { clase, user }
  }
}
  1. 优化数据库查询性能
  • 给高频查询字段加索引:
// 在对应Model文件中添加索引
// classModel.js
classSchema.index({ id: 1 });
// courseModel.js
courseSchema.index({ id: 1 });
// userModel.js
userSchema.index({ email: 1 });
  • 简化getClassById查询逻辑,避免内存过滤:
export async function getClassById(id, courseId) {
  try {
    const clase = await Class.findOne({ id })
      .populate({
        path: 'course',
        match: { id: courseId }
      })
      .exec();
    return clase?.course ? JSON.parse(JSON.stringify(clase)) : null;
  } catch (err) {
    console.log(err)
  }
}
  1. 添加超时与容错处理
  • 给MongoDB连接添加超时限制:
const options = {
  serverSelectionTimeoutMS: 5000, // 5秒未连接成功则抛出错误
  socketTimeoutMS: 5000 // 读写操作超时时间
};
  • 完善updateActualCourseSS的空值处理:
export async function updateActualCourseSS(email: string, 
  courseId: string, actualChapter: number) {
  try {
    if (!email) return null;
    const user: any | null = await User.findOne({ email }).exec()
    if (!user) return null;
    const courseDB: CoursesDB | null = await Course.findOne({ id: courseId}).exec()
    if (!courseDB) return user;

    const index = user.courses.findIndex((course: CourseUser) => 
      course.course.valueOf() === courseDB._id.valueOf())

    if (index !== -1) {
      user.courses[index].actualChapter = actualChapter;
      user.courses[index].actualTime = 0;
      await user.save();
    }

    return JSON.parse(JSON.stringify(user));
  } catch (err) {
    console.log(err)
    return null;
  }
}

内容的提问来源于stack exchange,提问作者Mateo Molfino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 16:40:13