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

NodeJS中导出依赖异步响应的常量问题

在Next.js + TypeScript中实现常量级异步环境变量管理

方案1:应用启动前预加载环境变量(自定义服务器场景)

通过在服务器启动阶段异步加载所有环境变量并缓存为常量对象,确保后续代码使用的都是已解析的最终值,同时利用as const标记为只读防止篡改。

// env-loader.ts
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";

const ssmClient = new SSMClient({ region: process.env.AWS_REGION });

// 封装SSM获取逻辑,失败则回退到process.env
async function getEnvVar(key: string, fallback?: string): Promise<string | undefined> {
  try {
    const command = new GetParameterCommand({
      Name: `/your-app/${process.env.NODE_ENV}/${key}`,
      WithDecryption: true,
    });
    const response = await ssmClient.send(command);
    return response.Parameter?.Value;
  } catch {
    return fallback ?? process.env[key];
  }
}

// 预加载所有需要的环境变量,返回只读常量Promise
export const ENV = (async () => {
  return {
    PGHOST: await getEnvVar("PGHOST", process.env.PGHOST),
    PGUSER: await getEnvVar("PGUSER", process.env.PGUSER),
    // 按需添加其他环境变量
  } as const;
})();

在自定义服务器入口等待变量加载完成后启动服务:

// server.ts
import { ENV } from "./env-loader";
import next from "next";
import { createServer } from "http";

const startServer = async () => {
  // 等待环境变量加载完成
  await ENV;
  
  const app = next({ dev: process.env.NODE_ENV === "development" });
  const handle = app.getRequestHandler();

  app.prepare().then(() => {
    createServer((req, res) => handle(req, res)).listen(3000, () => {
      console.log("Server running on http://localhost:3000");
    });
  });
};

startServer();

后续业务代码中使用时,只需导入ENV并await(因启动时已预加载,后续await会立即返回结果):

// 业务文件
import { ENV } from "./env-loader";

async function connectDB() {
  const { PGHOST } = await ENV;
  // 使用PGHOST连接数据库
}

方案2:Singleton只读类(App Router/Pages Router场景)

通过单例模式封装环境变量加载逻辑,对外暴露只读getter,确保变量加载一次后缓存,且无法被篡改。

// env.ts
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";

class Env {
  private static instance: Env;
  private envVars: Record<string, string | undefined> | null = null;

  private constructor() {}

  // 获取单例实例,首次调用时自动加载环境变量
  public static async getInstance(): Promise<Env> {
    if (!Env.instance) {
      Env.instance = new Env();
      await Env.instance.loadEnvVars();
    }
    return Env.instance;
  }

  // 异步加载环境变量
  private async loadEnvVars() {
    const ssmClient = new SSMClient({ region: process.env.AWS_REGION });
    const getParam = async (key: string) => {
      try {
        const command = new GetParameterCommand({
          Name: `/your-app/${process.env.NODE_ENV}/${key}`,
          WithDecryption: true,
        });
        const resp = await ssmClient.send(command);
        return resp.Parameter?.Value;
      } catch {
        return process.env[key];
      }
    };

    this.envVars = {
      PGHOST: await getParam("PGHOST"),
      PGUSER: await getParam("PGUSER"),
      // 其他变量
    };
  }

  // 只读getter,确保变量无法被修改
  public get PGHOST(): string | undefined {
    if (!this.envVars) throw new Error("环境变量未加载完成");
    return this.envVars.PGHOST;
  }

  // 其他环境变量的getter
  public get PGUSER(): string | undefined {
    if (!this.envVars) throw new Error("环境变量未加载完成");
    return this.envVars.PGUSER;
  }
}

// 导出预加载的单例Promise
export const env = Env.getInstance();

在服务器组件中使用:

// app/page.tsx
import { env } from "../env";

export default async function Home() {
  const loadedEnv = await env;
  return <div>数据库地址:{loadedEnv.PGHOST}</div>;
}

方案3:构建时注入环境变量(静态场景)

如果环境变量在构建阶段即可确定,可通过预构建脚本从SSM拉取变量并写入.env.local,让Next.js自动加载为常量级环境变量。

// prebuild.js
const { SSMClient, GetParameterCommand } = require("@aws-sdk/client-ssm");
const fs = require("fs");

async function main() {
  const ssmClient = new SSMClient({ region: process.env.AWS_REGION });
  const envVars = {};

  const getParam = async (key) => {
    try {
      const command = new GetParameterCommand({
        Name: `/your-app/${process.env.NODE_ENV}/${key}`,
        WithDecryption: true,
      });
      const resp = await ssmClient.send(command);
      return resp.Parameter?.Value;
    } catch {
      return process.env[key];
    }
  };

  envVars.PGHOST = await getParam("PGHOST");
  envVars.PGUSER = await getParam("PGUSER");

  // 将变量写入.env.local
  const envContent = Object.entries(envVars)
    .map(([key, value]) => `${key}=${value ?? ""}`)
    .join("\n");
  fs.writeFileSync(".env.local", envContent);
}

main();

修改package.json脚本,在构建/启动前执行预加载:

{
  "scripts": {
    "prebuild": "node prebuild.js",
    "build": "next build",
    "dev": "node prebuild.js && next dev"
  }
}

此时业务代码可直接使用process.env.PGHOST,类型为string | undefined,且在客户端代码中会被静态替换为常量,服务器端可通过Object.freeze(process.env)进一步防止篡改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:59:56