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

如何在NextJS中实现单例?解决Server Action重复初始化问题

在Next.js App Router中实现配置单例的可行方案

问题根源

在Next.js App Router里,Server Action默认可能运行在无状态的Edge Runtime中,每个请求都是独立隔离环境;即使使用Node.js Runtime,也可能因进程隔离或模块加载策略,导致传统单例/全局变量方案失效,每次请求都会重新初始化配置,造成性能损耗。


解决方案1:切换Node.js Runtime + 模块级单例

利用Node.js的模块缓存机制,让配置在模块第一次加载时完成初始化,后续所有导入直接复用实例。

步骤1:指定Server Action使用Node.js Runtime

在Server Action文件顶部添加Runtime声明:

"use server"
export const runtime = 'nodejs' // 强制使用Node.js Runtime

import { config } from "@/lib/constants"

export async function testConst() {
    return config.customerSettings.getCustomerText()
}

步骤2:调整单例实现,模块加载时完成初始化

// lib/customerSettings.ts
import { cleanEnv, str } from "envalid"
import { CustomerSpecifficLangaugeKey, SupportedLanguage } from "./sharedTypes"

class CustomerSettings {
    private readonly customerText: Record<SupportedLanguage, Record<CustomerSpecifficLangaugeKey, string>>

    private constructor(customerText: typeof this.customerText) {
        console.log("===initialize CustomerSettings===")
        this.customerText = customerText
    }

    public getCustomerText() {
        return this.customerText
    }
}

// 模块加载时一次性初始化,Node.js会缓存该模块
const validatedEnv = cleanEnv(process.env, {
    // 填入你的环境变量验证规则
    CUSTOMER_TEXT_EN: str(),
    CUSTOMER_TEXT_DE: str()
})

// 转换为业务需要的结构
const customerText: Record<SupportedLanguage, Record<CustomerSpecifficLangaugeKey, string>> = {
    en: {
        welcome: validatedEnv.CUSTOMER_TEXT_EN
    },
    de: {
        welcome: validatedEnv.CUSTOMER_TEXT_DE
    }
}

// 导出已初始化的单例
export const customerSettings = new CustomerSettings(customerText)

步骤3:更新全局配置模块

// lib/constants.ts
import { customerSettings } from "./customerSettings"

export const config = {
    customerSettings,
    // 其他全局配置项
}

解决方案2:用globalThis存储进程级单例(仅Node.js Runtime)

如果需要动态初始化逻辑,可通过globalThis在Node.js进程内共享实例:

// lib/customerSettings.ts
import { cleanEnv, str } from "envalid"
import { CustomerSpecifficLangaugeKey, SupportedLanguage } from "./sharedTypes"

export default class CustomerSettings {
    private static instance: CustomerSettings | null = null
    private readonly customerText: Record<SupportedLanguage, Record<CustomerSpecifficLangaugeKey, string>>

    private constructor(customerText: typeof this.customerText) {
        console.log("===initialize CustomerSettings===")
        this.customerText = customerText
    }

    public getCustomerText() {
        return this.customerText
    }

    public static fromEnv(): CustomerSettings {
        const globalKey = 'CustomerSettingsInstance'
        // 优先从globalThis读取已存在的实例
        if ((globalThis as any)[globalKey]) {
            return (globalThis as any)[globalKey]
        }

        const validatedEnv = cleanEnv(process.env, {
            // 环境变量验证规则
        })
        const customerText = { /* 转换逻辑 */ }
        const instance = new CustomerSettings(customerText)
        // 存入globalThis,进程内复用
        (globalThis as any)[globalKey] = instance
        return instance
    }
}

解决方案3:构建时预生成配置(性能最优)

如果配置完全依赖环境变量、无动态逻辑,可在构建阶段直接生成配置文件,彻底避免运行时初始化开销:

步骤1:创建配置生成脚本

// scripts/generate-config.js
const fs = require('fs')
const { cleanEnv, str } = require('envalid')

// 验证环境变量
const validatedEnv = cleanEnv(process.env, {
    CUSTOMER_TEXT_EN: str(),
    CUSTOMER_TEXT_DE: str()
})

// 生成TS格式的配置代码
const configContent = `
import { CustomerSpecifficLangaugeKey, SupportedLanguage } from "../lib/sharedTypes"

export class CustomerSettings {
    getCustomerText() {
        return ${JSON.stringify({
            en: { welcome: validatedEnv.CUSTOMER_TEXT_EN },
            de: { welcome: validatedEnv.CUSTOMER_TEXT_DE }
        } as Record<SupportedLanguage, Record<CustomerSpecifficLangaugeKey, string>>)}
    }
}

export const config = {
    customerSettings: new CustomerSettings()
}
`

// 写入生成的配置文件
fs.writeFileSync('./lib/generated-config.ts', configContent)

步骤2:修改package.json脚本

让构建/开发命令先执行配置生成:

{
    "scripts": {
        "prebuild": "node scripts/generate-config.js",
        "build": "next build",
        "dev": "node scripts/generate-config.js && next dev"
    }
}

步骤3:导入生成的配置

"use server"
export const runtime = 'nodejs'

import { config } from "@/lib/generated-config"

export async function testConst() {
    return config.customerSettings.getCustomerText()
}

注意事项

  1. 开发环境下Next.js热重载可能导致模块重新加载,触发多次初始化,这是正常现象,生产环境不会出现。
  2. 使用方案3时,需确保部署平台(如Vercel)在构建阶段能获取到所需环境变量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:05:56