🤦♂️
環境変数の呼び出し方、結局どう書けばいいの?(Node.js/Next.js編)
はじめに
社内のプロジェクト間でも、環境変数の値にアクセスする際の書き方が統一されていないことはありませんか?私自身、その時々でいつも微妙に違う書き方になってしまっている事が多いです。
本記事では、よくある記述とアプローチの1つとして、最近書いたコードを紹介します。
追記: 2024/12/19
少し修正したのをnpm package
にしました
よくあるケース
環境変数
.env
MY_VALUE="xxx"
1. process.env.*** を直接記述する
xxx.config.ts
const text: string = process.env.MY_VALUE || "";
- ※ 未定義時の考慮が必要
2. 環境変数を ***.d.ts に型定義する
global.d.ts
declare namespace NodeJS {
interface ProcessEnv {
MY_VALUE: string;
}
}
xxx.config.ts
const text: string = process.env.MY_VALUE;
- 冗長な
デフォルト値
の記述が不要になる - ※ 未定義時の考慮が必要
3. 環境変数が未定義の場合にエラーを投げる
xxx.config.ts
if (process.env.MY_VALUE === undefined) {
throw new Error("MY_VALUE is not defined");
}
const text: string = process.env.MY_VALUE;
- ※ 必須の環境変数ごとに記述が必要
X. その他ケース:...
...
最近書いたケース
環境変数
.env
NODE_ENV="development"
PORT="3000"
TRANSFORMED_VAR="lowercase"
BOOLEAN_FLAG="true"
サンプルコード
プロジェクトにzod
が使われていることが多いので、環境変数のバリデーション
に使用しました。
用意した Validate 関数(process.env向け)
libs/dotenv.ts
import type { ZodSchema } from "zod";
import { z } from "zod";
export function validateEnv<T extends ZodSchema>(
schema: T,
env: NodeJS.ProcessEnv | Record<string, string | undefined> = process.env,
): z.infer<T> {
const result = schema.safeParse(env);
if (!result.success) {
console.error(
"Environment variable validation failed:",
result.error.format(),
);
throw new Error("Invalid environment variables");
}
return result.data;
}
// Custom methods
const envNumber = () =>
z.preprocess((v) => Number(v) || 0, z.number().int().nonnegative());
const envBoolean = () => z.preprocess((v) => Boolean(v), z.boolean());
const g = {
envObject: z.object,
envEnum: z.enum,
envString: z.string,
envNumber,
envBoolean,
};
export { g };
近しい記事:https://catalins.tech/validate-environment-variables-with-zod/
-
.env
向けのカスタムメソッド(string
|number
|boolean
|enum
)をexport
利用例
usage.ts
import { g, validateEnv } from "...";
const schema = g.envObject({
NODE_ENV: g.envEnum(["development", "production", "test"]),
PORT: g.envNumber(),
TRANSFORMED_VAR: g.envString().transform((val) => val.toUpperCase()),
OPTIONAL_VAR: g.envString().optional(),
BOOLEAN_FLAG: g.envBoolean(),
});
export const env = validateEnv(schema);
// NODE_ENV: "development"
// PORT: 3000
// TRANSFORMED_VAR: "LOWERCASE"
// BOOLEAN_FLAG: true
-
型チェック
、型変換(必要に応じて)
するスキーマを定義 - 環境変数の値にアクセスする際はバリデーション関数(
validateEnv
)を通した値を利用する-
型安全
、型変換
-
Next.js編
Client Component
、Server Component
からアクセスできる環境変数ごとにスキーマを定義
環境変数
.env
NEXT_PUBLIC_MY_VALUE="xxx"
MY_SECRET="***"
Client Component
用
utils/dotenv.public.ts
import { g, validateEnv } from "@/libs/dotenv";
export const schema = g.envObject({
NEXT_PUBLIC_MY_VALUE: g.envString(),
});
export const env = validateEnv(schema, {
/**
* Next.js will replace process.env.customKey with 'my-value' at build time. Trying to destructure process.env variables won't work due to the nature of webpack
* https://nextjs.org/docs/pages/api-reference/next-config-js/env
*/
NEXT_PUBLIC_MY_VALUE: process.env.NEXT_PUBLIC_MY_VALUE,
});
// Client Component
// import { env } from "@/utils/dotenv.public";
// console.log(env.NEXT_PUBLIC_MY_VALUE); // "xxx"
- ※ フルパス(
process.env.NEXT_PUBLIC_XXX
)で記述しないと環境変数の値にアクセス出来ないので記述しています - ※ 場合によっては
constants.ts
の様な定数を管理するファイルで事足りるかもしれません
Server Component
用
utils/dotenv.ts
import { g, validateEnv } from "@/libs/dotenv";
import { schema as publicSchema } from "@/utils/dotenv.public";
const schema = g.envObject({
MY_SECRET: g.envString(),
});
export const env = validateEnv(publicSchema.merge(schema));
// Server Component
// import { env } from "@/utils/dotenv";
// console.log(env.NEXT_PUBLIC_MY_VALUE); // "xxx"
// console.log(env.MY_SECRET); // "***"
-
Client Component
の環境変数もアクセスできるのでスキーマをマージしました
さいごに
他にもスマートな書き方
や、こんな書き方があったよ
など、教えていただけますと幸いです。
Discussion