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

如何避免Next.js多次实例化单例类/对象?

问题分析

你的问题本质是Next.js API路由的无状态特性导致的:每个API请求都会在独立的Node.js进程中处理,不同请求(甚至同一路由的多次请求)之间的内存完全隔离。你当前的单例Analytics实例只存在于单个请求的内存中,请求结束后就会被销毁,所以/api/e1设置的数据无法被/api/e2读取。

解决方案

下面是几种可行的实现方式,按生产环境适用性排序:

1. 使用外部持久化存储(推荐生产环境)

把Analytics的数据存储到外部服务(比如Redis、MongoDB、Firebase Firestore),这样所有请求都能共享统一的数据源。

改造示例(Redis为例)

首先安装Redis依赖:

npm install redis

改造Analytics类:

import { createClient } from 'redis';

class Analytics {
    private client;

    constructor() {
        this.client = createClient({
            // 生产环境建议用环境变量配置连接信息
            url: process.env.REDIS_URL || 'redis://localhost:6379'
        });
        this.client.connect().catch(console.error);
    }

    async setPaths(identifier: string) {
        const data = {
            generic: getGenericInit(),
            session: getSessionInit(),
            paths: {
                geoCollectionPath: getGeoPath(identifier),
                sessionCollectionPath: getSessionPath(identifier),
                eventsCollectionPath: getEventPath(identifier)
            }
        };
        await this.client.set(`analytics:${identifier}`, JSON.stringify(data));
    }

    async getAll() {
        const keys = await this.client.keys('analytics:*');
        const result: Record<string, IData> = {};
        for (const key of keys) {
            const identifier = key.replace('analytics:', '');
            const dataStr = await this.client.get(key);
            if (dataStr) {
                result[identifier] = JSON.parse(dataStr);
            }
        }
        return result;
    }
}

const analytics = new Analytics();
export default analytics;

修改API路由为异步函数:
e1.ts:

import { NextApiHandler } from 'next';
import analytics from '@/firebase/v2/analytics';

const handler: NextApiHandler = async (req, res) => {
    await analytics.setPaths('abc');
    return res.status(201).end();
};
export default handler;

e2.ts:

import { NextApiHandler } from 'next';
import analytics from '@/firebase/v2/analytics';

const handler: NextApiHandler = async (req, res) => {
    const data = await analytics.getAll();
    return res.status(200).json(data);
};
export default handler;

2. 使用Next.js缓存机制(适用于非高频更新数据)

如果你的数据不需要实时强一致性,可以用Next.js的缓存策略,适合只读或低频更新的场景:

// app/api/analytics/route.ts
import { unstable_cache } from 'next/cache';

// 缓存数据,60秒重新验证一次
const getAnalyticsData = unstable_cache(async () => {
    // 从外部存储或其他数据源获取数据
}, ['analytics-data'], { revalidate: 60 });

export async function GET() {
    const data = await getAnalyticsData();
    return Response.json(data);
}

3. 开发环境临时用全局变量(不推荐生产)

开发环境中Next.js可能复用进程,你可以把数据挂载到Node.js全局对象,但生产环境多进程部署时完全不可靠:

// 扩展全局类型
declare global {
    namespace NodeJS {
        interface Global {
            analyticsData: Record<string, IData>;
        }
    }
}

class Analytics {
    data: Record<string, IData>;

    constructor() {
        this.data = global.analyticsData || {};
        global.analyticsData = this.data;
    }

    setPaths(identifier: string) {
        if (!this.data[identifier])
            this.data[identifier] = {
                generic: getGenericInit(),
                session: getSessionInit(),
                paths: {
                    geoCollectionPath: '',
                    sessionCollectionPath: '',
                    eventsCollectionPath: ''
                }
            };
        this.data[identifier].paths = {
            geoCollectionPath: getGeoPath(identifier),
            sessionCollectionPath: getSessionPath(identifier),
            eventsCollectionPath: getEventPath(identifier)
        };
    }

    getAll() {
        return this.data;
    }
}

const analytics = new Analytics();
export default analytics;
关键提醒

生产环境绝对不能依赖内存共享(单例、全局变量),因为Next.js会采用多进程或无服务器部署,内存无法跨实例共享,外部存储是唯一可靠的跨请求/路由共享数据的方式。

内容的提问来源于stack exchange,提问作者Shivam Sahil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 20:30:43