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

NextJS 13中间件中Singleton构造函数多次执行的问题与解决

Next.js 中单例类构造函数重复执行的问题分析与解决

问题重现

定义了如下单例类 Paths:

export default class Paths {
  private static _instance: Paths;

  private constructor() {
    console.log('paths constructor');
  }

  public static get Instance() {
    console.log(this._instance);
    return this._instance || (this._instance = new this());
  }
}

在Next.js中间件中使用该单例:

import { NextRequest, NextResponse } from 'next/server';
import Paths from './lib/features/paths';

export default async function middleware(req: NextRequest) {
  const paths = Paths.Instance;

  return NextResponse.next();
}

刷新页面时,服务器控制台重复输出:

undefined
paths constructor

说明Paths的构造函数被多次执行,单例逻辑失效。

原因分析

  1. 开发模式热重载影响:Next.js开发服务器(next dev)默认启用Fast Refresh特性,当相关文件变更(甚至部分无文件变更的场景),模块会被重新编译加载,导致Paths类的静态属性_instance被重置为undefined,每次请求都会触发新实例创建。
  2. Edge Runtime上下文隔离:Next.js中间件默认运行在Edge Runtime环境,该环境为保证请求隔离,可能会在不同执行上下文加载模块,导致类的静态属性无法跨请求共享。
  3. 模块缓存机制失效:尽管Node.js默认缓存模块,但Next.js开发模式或Edge Runtime的特殊处理,可能绕过模块缓存,使得Paths模块被多次加载,每次加载都会重新初始化类的静态成员。

解决方案

方案1:改用模块级单例(推荐)

将单例逻辑从类的静态属性改为模块导出的单例对象,利用模块缓存机制保证单例唯一性:

// paths.ts
class Paths {
  constructor() {
    console.log('paths constructor');
  }
  // 在此添加类的业务方法
}

const pathsInstance = new Paths();
export default pathsInstance;

中间件中直接导入使用:

import { NextRequest, NextResponse } from 'next/server';
import paths from './lib/features/paths';

export default async function middleware(req: NextRequest) {
  // 直接使用已初始化的paths实例
  return NextResponse.next();
}

模块级导出的实例会被缓存,只要缓存生效,无论模块被加载多少次,都只会创建一次实例。

方案2:利用全局对象存储实例(仅Node.js环境适用)

若中间件运行在Node.js Runtime(而非默认Edge Runtime),可将实例存储在全局对象中,避免模块重载导致的重置:

export default class Paths {
  private static _instance: Paths;

  private constructor() {
    console.log('paths constructor');
  }

  public static get Instance() {
    // 检查全局对象中是否存在实例
    if (globalThis._pathsInstance) {
      return globalThis._pathsInstance as Paths;
    }
    this._instance = new this();
    globalThis._pathsInstance = this._instance;
    return this._instance;
  }
}

// 补充全局类型定义(可选)
declare global {
  var _pathsInstance: Paths | undefined;
}

注意:该方案不适用于Edge Runtime,因为Edge环境限制了全局对象的使用。

方案3:关闭开发模式Fast Refresh(不推荐)

可在next.config.js中关闭Fast Refresh避免模块重载,但会牺牲开发体验:

/** @type {import('next').NextConfig} */
const nextConfig = {
  fastRefresh: false,
};

module.exports = nextConfig;

补充说明

生产环境下,Next.js会对模块进行打包优化,模块缓存机制正常工作,基于类的静态单例可能恢复正常,但仍推荐使用模块级单例保证跨环境一致性。

内容的提问来源于stack exchange,提问作者Nicola Scionti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:45:39