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
相关产品推荐
相关产品推荐

