環境変数の管理は、アプリケーション開発において避けて通れない課題です。process.env.API_KEY のような参照は型安全ではなく、タイポや未設定の変数によるランタイムエラーの原因になります。T3 Env は、Zod を使って環境変数を検証し、完全な型安全性を提供するライブラリです。
T3 Env とは
T3 Env は、以下の機能を提供する環境変数管理ライブラリです:
- 型安全性: すべての環境変数に TypeScript の型が付与される
- ランタイム検証: Zod スキーマによるビルド時・起動時の検証
- サーバー/クライアント分離: サーバー専用変数がクライアントに漏れることを防止
- 開発体験の向上: IDE での補完とタイポの即座な検出
ステップ1: インストール
Next.js プロジェクトの場合
# npm
npm install @t3-oss/env-nextjs zod
# pnpm
pnpm add @t3-oss/env-nextjs zod
# yarn
yarn add @t3-oss/env-nextjs zod
# bun
bun add @t3-oss/env-nextjs zodその他のフレームワークの場合
# コアパッケージを使用
npm install @t3-oss/env-core zodステップ2: 環境変数スキーマの定義
基本的な設定ファイルの作成
src/env.ts(または env.ts)を作成します:
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/**
* サーバーサイド専用の環境変数
* クライアントからアクセスするとエラーになります
*/
server: {
DATABASE_URL: z.string().url(),
API_SECRET_KEY: z.string().min(1),
NODE_ENV: z.enum(["development", "production", "test"]),
},
/**
* クライアントサイドでも使用可能な環境変数
* NEXT_PUBLIC_ プレフィックスが必須です
*/
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_SITE_NAME: z.string().min(1),
},
/**
* Next.js 13.4.4 以降では experimental__runtimeEnv を使用
* クライアント変数のみ指定すれば OK
*/
experimental__runtimeEnv: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_SITE_NAME: process.env.NEXT_PUBLIC_SITE_NAME,
},
});Next.js 13.4.4 未満の場合
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
// すべての変数を手動で指定
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});ステップ3: アプリケーションでの使用
インポートして使用
import { env } from "@/env";
// サーバーコンポーネント・API ルートで使用
export async function GET() {
const dbUrl = env.DATABASE_URL; // 型: string
// ...
}
// クライアントコンポーネントで使用
export default function Component() {
return <div>API: {env.NEXT_PUBLIC_API_URL}</div>;
}サーバー変数へのクライアントアクセス防止
// クライアントコンポーネントで以下を実行すると
// ランタイムエラーが発生します
const secret = env.API_SECRET_KEY; // エラー!ステップ4: 高度なスキーマ定義
数値への変換
server: {
PORT: z
.string()
.transform((s) => parseInt(s, 10))
.pipe(z.number()),
// または Zod の coerce を使用
MAX_CONNECTIONS: z.coerce.number(),
}真偽値への変換
server: {
// "true" / "false" のみ許可
ENABLE_FEATURE: z
.string()
.refine((s) => s === "true" || s === "false")
.transform((s) => s === "true"),
// より緩い変換("0" や "false" を false に)
DEBUG_MODE: z
.string()
.transform((s) => s !== "false" && s !== "0"),
}オプショナルな変数
server: {
// 未設定でも OK
OPTIONAL_KEY: z.string().optional(),
// デフォルト値を設定
LOG_LEVEL: z.string().default("info"),
}特定のパターンの検証
server: {
// sk_ で始まる文字列のみ許可
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
// メールアドレス形式
ADMIN_EMAIL: z.string().email(),
// URL 形式
WEBHOOK_URL: z.string().url(),
}ステップ5: 空文字列の処理
.env ファイルで PORT= のように空文字列が設定されている場合の対処:
export const env = createEnv({
server: {
PORT: z.coerce.number().default(3000),
},
// 空文字列を undefined として扱う(推奨)
emptyStringAsUndefined: true,
// ...
});ステップ6: エラーハンドリングのカスタマイズ
バリデーションエラーのカスタマイズ
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
},
// バリデーション失敗時のハンドラー
onValidationError: (issues) => {
console.error("❌ 環境変数の検証に失敗しました:");
for (const issue of issues) {
const path = issue.path?.join(".") || "unknown";
console.error(` - ${path}: ${issue.message}`);
}
process.exit(1);
},
// クライアントからサーバー変数へのアクセス時
onInvalidAccess: (variable) => {
throw new Error(
`🚨 サーバー変数 "${variable}" にクライアントからアクセスしようとしました`
);
},
// ...
});ステップ7: バリデーションのスキップ
Docker ビルド時など、すべての環境変数が揃っていない状況での対処:
export const env = createEnv({
// ...
skipValidation: process.env.SKIP_ENV_VALIDATION === "true",
});注意: バリデーションをスキップすると、型とランタイム値が一致しなくなる可能性があります。本番環境では必ず検証を有効にしてください。
ステップ8: プリセットの使用
Vercel プリセット
import { createEnv } from "@t3-oss/env-nextjs";
import { vercel } from "@t3-oss/env-nextjs/presets-zod";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
// Vercel の環境変数を自動的に追加
extends: [vercel()],
experimental__runtimeEnv: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
// Vercel 固有の変数にアクセス可能
console.log(env.VERCEL_URL);
console.log(env.VERCEL_ENV);利用可能なプリセット
| プリセット | 説明 |
|---|---|
vercel() | Vercel プラットフォーム変数 |
railway() | Railway プラットフォーム |
render() | Render.com |
fly() | Fly.io |
netlify() | Netlify |
upstashRedis() | Upstash Redis |
uploadthingV6() | UploadThing v6 |
ステップ9: 複数の設定を組み合わせる
import { createEnv } from "@t3-oss/env-nextjs";
import { vercel } from "@t3-oss/env-nextjs/presets-zod";
import { z } from "zod";
// データベース用のプリセット
const databaseEnv = createEnv({
server: {
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.coerce.number().default(10),
},
runtimeEnv: process.env,
});
// 認証用のプリセット
const authEnv = createEnv({
server: {
AUTH_SECRET: z.string().min(32),
AUTH_URL: z.string().url(),
},
runtimeEnv: process.env,
});
// メインの設定
export const env = createEnv({
server: {
APP_NAME: z.string(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
extends: [vercel(), databaseEnv, authEnv],
experimental__runtimeEnv: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
// すべての変数にアクセス可能
env.DATABASE_URL;
env.AUTH_SECRET;
env.APP_NAME;
env.VERCEL_URL;ステップ10: 推奨されるプロジェクト構成
ファイル構成
src/
├── env.ts # T3 Env の設定
├── app/
│ ├── layout.tsx
│ └── page.tsx
└── lib/
└── db.ts # env を使用.env ファイルの例
# .env.local(開発用)
# サーバー変数
DATABASE_URL=postgresql://localhost:5432/mydb
API_SECRET_KEY=dev-secret-key-12345
# クライアント変数
NEXT_PUBLIC_API_URL=http://localhost:3000/api
NEXT_PUBLIC_SITE_NAME=My App.env.example の作成
# .env.example(テンプレート)
# サーバー変数
DATABASE_URL=
API_SECRET_KEY=
# クライアント変数
NEXT_PUBLIC_API_URL=
NEXT_PUBLIC_SITE_NAME=トラブルシューティング
ビルド時にエラーが発生する
環境変数が設定されていない場合、ビルドが失敗します:
❌ Invalid environment variables: {
DATABASE_URL: [ 'Required' ]
}解決策: CI/CD 環境で必要な環境変数を設定するか、skipValidation を使用してください。
クライアントで環境変数が undefined になる
NEXT_PUBLIC_ プレフィックスが付いているか確認してください:
// ❌ 間違い - クライアントからアクセスできない
client: {
API_URL: z.string().url(),
}
// ✅ 正しい
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
}型エラー: Property does not exist
env.ts が正しくインポートされているか確認してください:
// ❌ 間違い
import env from "./env";
// ✅ 正しい
import { env } from "./env";まとめ
T3 Env を導入することで:
- 型安全性: すべての環境変数に型が付き、IDE 補完が効く
- 早期エラー検出: 起動時に設定ミスを検出できる
- セキュリティ向上: サーバー変数がクライアントに漏れることを防止
- 保守性向上: 環境変数のドキュメントとしても機能
既存プロジェクトへの導入も簡単で、段階的に移行することも可能です。まずは重要な環境変数から始めて、徐々にすべての変数を T3 Env で管理するようにしましょう。