Next.js 15自定义服务器Jest测试遇CustomGC打开句柄错误如何解决?
问题描述
使用自定义Next.js 15服务器,代码如下:
import { DotenvPopulateInput } from "@/utilities/dotenv" import { createServer, Server } from "http" import next from "next" let server: null | Server export const startServer = async (env?: DotenvPopulateInput) => { const app = next({}) await app.prepare() Object.assign(process.env, env) server = createServer(app.getRequestHandler()).listen( parseInt(process.env.PORT || "3000") ) } export const stopServer = async (): Promise<void> => { return new Promise((resolve, reject) => { if (server) { server.close((error) => { if (error) { reject(error) } else { resolve() } }) server = null } else { resolve() } }) } export const restartServer = async (env?: DotenvPopulateInput) => { await stopServer() await startServer(env) }
所有Jest测试均通过,但出现如下打开句柄错误:
Jest has detected the following 1 open handle potentially keeping Jest from exiting: ● CustomGC at Runtime._loadModule (node_modules/jest-runtime/build/index.js:1018:29) at loadNative (node_modules/next/src/build/swc/index.ts:1149:20) at node_modules/next/src/build/swc/index.ts:242:22 at loadBindings (node_modules/next/src/build/swc/index.ts:196:21) at transform (node_modules/next/src/build/swc/index.ts:1284:24) at transpileConfig (node_modules/next/src/build/next-config-ts/transpile-config.ts:60:28) at async loadConfig (node_modules/next/src/server/config.ts:1156:28) at async initialize (node_modules/next/src/server/lib/router-server.ts:91:18) at async NextCustomServer.prepare (node_modules/next/src/server/next.ts:351:24)
请问该如何修复或排查此问题?
解决方案
1. 补充Next.js应用实例的资源清理
当前stopServer仅关闭了HTTP服务器,但Next.js的app实例内部还有未释放的资源(比如SWC绑定、配置加载句柄)。需要保存app实例并调用其close()方法(Next.js 13+版本支持):
修改服务器代码,新增保存nextApp实例的逻辑:
import { DotenvPopulateInput } from "@/utilities/dotenv" import { createServer, Server } from "http" import next from "next" let server: null | Server; let nextApp: null | ReturnType<typeof next> = null; export const startServer = async (env?: DotenvPopulateInput) => { nextApp = next({}) await nextApp.prepare() Object.assign(process.env, env) server = createServer(nextApp.getRequestHandler()).listen( parseInt(process.env.PORT || "3000") ) } export const stopServer = async (): Promise<void> => { return new Promise(async (resolve, reject) => { try { // 先关闭Next.js内部资源 if (nextApp) { await nextApp.close(); nextApp = null; } // 再关闭HTTP服务器 if (server) { server.close((error) => { if (error) { reject(error) } else { resolve() } }) server = null } else { resolve() } } catch (err) { reject(err); } }) } export const restartServer = async (env?: DotenvPopulateInput) => { await stopServer() await startServer(env) }
2. 确保测试中正确执行清理逻辑
在Jest测试文件中,通过afterAll或afterEach钩子确保服务器资源被完全清理,且使用async/await等待清理完成:
// 测试文件示例 import { startServer, stopServer } from "../path/to/server"; beforeAll(async () => { await startServer(); }); afterAll(async () => { await stopServer(); });
3. 兜底:强制退出进程(仅必要时使用)
如果上述方法仍无法解决,可在测试清理阶段强制退出Node进程,但这是兜底方案,优先修复资源泄漏:
afterAll(async () => { await stopServer(); process.exit(0); });
4. 深入排查句柄泄漏
- 运行Jest时添加
--detectOpenHandles --verbose参数,获取更详细的句柄泄漏信息; - 使用Node.js的
async_hooks模块追踪句柄的创建和销毁,定位具体泄漏点; - 检查Next.js配置中SWC相关的持久化设置,临时禁用
swcMinify等特性验证是否为SWC导致的问题。
内容的提问来源于stack exchange,提问作者sunknudsen
相关产品推荐
相关产品推荐

