如何在 Next.js 应用启动时仅执行一次数据库连接(而非每次请求都重连)

2次阅读

如何在 Next.js 应用启动时仅执行一次数据库连接(而非每次请求都重连)

Next.js 中无法像 express 那样在入口文件统一初始化 mongodb 连接,但可通过检查 mongoose.connection.readyState 实现单例连接逻辑,确保应用生命周期内只建立一次连接。

next.js 中无法像 express 那样在入口文件统一初始化 mongodb 连接,但可通过检查 `mongoose.connection.readystate` 实现单例连接逻辑,确保应用生命周期内只建立一次连接。

在 Next.js(尤其是 App router)中,服务端组件、API 路由或服务器动作的代码并非全局单例执行——它们可能被多次导入、热重载触发、或在不同 serverless 实例/边缘函数中独立运行。因此,直接在 app/route.ts 或组件中调用 mongoose.connect() 会导致重复连接、资源泄漏甚至 ECONNREFUSED 错误。

✅ 正确做法是:封装一个可复用的数据库连接模块,并在每次使用前检查连接状态,仅当未连接时才发起连接。这本质上实现了“懒连接 + 单例保障”,符合 Next.js 的运行时模型。

✅ 推荐实现:lib/mongodb.ts

// lib/mongodb.ts import mongoose from 'mongoose';  const MONGODB_URI = process.env.MONGODB_URI;  if (!MONGODB_URI) {   throw new Error('Please define the MONGODB_URI environment variable.'); }  let cached = global.mongoose;  if (!cached) {   cached = global.mongoose = { conn: null, promise: null }; }  export async function connectToDatabase() {   if (cached.conn) {     return cached.conn;   }    if (!cached.promise) {     const opts = {       bufferCommands: false,       dbName: 'user', // 可选:指定默认数据库     };      cached.promise = mongoose       .connect(MONGODB_URI, opts)       .then((mongoose) => {         console.log('✅ MongoDB connected successfully');         return mongoose;       });   }    try {     cached.conn = await cached.promise;   } catch (e) {     cached.promise = null;     throw e;   }    return cached.conn; }

? 关键设计说明:

  • 利用 globalThis(Node.js 环境下等价于 global)缓存连接实例,避免跨模块重复初始化;
  • cached.promise 确保并发请求下连接逻辑只执行一次(Promise 一旦 resolve 就不会重复触发);
  • bufferCommands: false 是最佳实践,防止连接未就绪时命令积压。

✅ 在 API Route 中安全使用

// app/api/user/route.ts import { NextResponse } from 'next/server'; import { connectToDatabase } from '@/lib/mongodb'; import User from '@/models/User'; // 假设已定义 Mongoose Model  export async function POST(request: Request) {   try {     // ✅ 每次请求都调用 connectToDatabase —— 但实际只连接一次     await connectToDatabase();      const body = await request.json();     const user = await User.create(body);      return NextResponse.json({ success: true, data: user }, { status: 201 });   } catch (error) {     console.error('API route error:', error);     return NextResponse.json(       { error: 'Failed to create user' },       { status: 500 }     );   } }

⚠️ 注意事项与最佳实践

  • 不要在客户端组件中调用:connectToDatabase() 必须严格限定在服务端环境(Server Components / Route Handlers / Server Actions),否则会暴露数据库凭证;
  • 环境变量需配置正确:MONGODB_URI 应包含用户名、密码及认证数据库(如 mongodb+srv://user:pass@cluster.mongodb.net/?authSource=admin);
  • 开发环境热重载兼容:global.mongoose 在热重载时可能残留,建议配合 process.env.NODE_ENV === ‘development’ 添加连接清理逻辑(进阶可选);
  • 生产部署验证:Vercel Serverless 函数冷启动时仍会首次触发连接,但后续调用将复用缓存连接——这是预期行为,无需担忧。

✅ 总结

Next.js 并不提供类似 Express 的“应用启动钩子”,但通过 全局缓存 + 连接状态校验 + Promise 懒求值,我们能以零侵入、高可靠的方式实现“一次连接、处处复用”。这不是妥协,而是适配现代服务端渲染与边缘函数架构的务实方案。坚持封装连接逻辑到独立模块,并始终通过 connectToDatabase() 统一接入,即可兼顾可维护性与性能。

text=ZqhQzanResources