Create Branch

Next.jsのCI/CDビルドで「DATABASE_URL is not defined」エラーが発生する原因と解決策

Published on 2026年1月2日

Next.js

はじめに

Next.js + GitHub Actionsの環境でCI/CDパイプラインを構築した際、ローカルでは問題なくビルドできるのに、CI/CD環境では以下のエラーが発生しました。

Error: DATABASE_URL is not defined
    at .next/server/app/api/[[...route]]/route.js

GitHub ActionsのRepository VariablesにはDATABASE_URLを設定済みなのに、なぜ?


最初の仮説環境変数の設定漏れ?

最初に疑ったのは、GitHub Actionsへの環境変数の設定漏れでした。

しかし、Repository Variablesには確かにDATABASE_URLが設定されています。

ここで重要な事実Repository Variablesは自動的にprocess.envに入らない

ワークフロー内で明示的に設定が必要です。

jobs:
  build:
    env:
      DATABASE_URL: ${{ vars.DATABASE_URL }}

しかし、これは対症療法でした。本当の問題は別にありました。


真の原因Next.jsのビルド時モジュール評価

エラーログをよく見ると、.next/server/app/api/[[...route]]/route.jsでエラーが発生しています。ビルド後のAPIルートです。

問題のコードを見てみましょう。

// infrastructure/database/index.ts
const databaseUrl = process.env.DATABASE_URL!;
export const db = createDatabaseClient(databaseUrl);

// api/users/create/index.ts
import { db } from '../infrastructure/database';
// ...
const userRepository = createUserRepository(db);

何が起きているか

Next.js ビルド時
    ↓
route.ts がインポートされる
    ↓
users/create/index.ts がインポートされる
    ↓
import { db } from ".../infrastructure/database"
    ↓
モジュールのトップレベルが即座に評価される
    ↓
process.env.DATABASE_URL が undefined
    ↓
エラー発生

Next.jsはビルド時にモジュールのトップレベルを評価します。

ローカルでは.envファイルがあるので問題ありませんが、CI/CD環境では.envはgitignoreされているため存在しません。


問題の本質を理解する

ここで考えるべきは

  • DBクライアントが必要なタイミング: APIが実行される時(実行時)
  • DBクライアントが不要なタイミング: ビルド時

APIルートのコードはビルド時には実行されません。にもかかわらず、トップレベルで初期化しているため、ビルド時に評価されてしまっています。


解決策遅延初期化パターン

「本当に必要なときだけ初期化する」パターンを採用します。

Before(問題のあるコード)

// モジュール読み込み時に即座に実行される
const databaseUrl = process.env.DATABASE_URL!;
export const db = createDatabaseClient(databaseUrl);

After(遅延初期化)

import { createDatabaseClient, DatabaseClient } from '...';

let _db: DatabaseClient | null = null;

export function getDb(): DatabaseClient {
  if (!_db) {
    const databaseUrl = process.env.DATABASE_URL;
    if (!databaseUrl) {
      throw new Error('DATABASE_URL is not defined');
    }
    _db = createDatabaseClient(databaseUrl);
  }
  return _db;
}

なぜこれで解決するか

タイミング Before After
ビルド時 createDatabaseClient(undefined)が実行される 関数定義のみ、中身は実行されない
API実行時 正常動作 getDb()が呼ばれて初期化される

関数の中に入れることで、実際に使われるまで実行を遅延させています。


これは公式に推奨されているのか?

Next.js公式ドキュメントには直接的な記載はありませんが、この問題は広く知られており、多くのGitHub Discussionで議論されています。


今後の実装で気をつけること

環境変数に依存するクライアントをNext.jsで実装する際は、遅延初期化パターンを使用しましょう。

対象となるクライアント

  • Database(PostgreSQL、MySQL等)
  • Auth0 / 認証サービス
  • Redis / キャッシュサービス
  • S3 / ストレージサービス
  • Stripe / 決済サービス
  • その他、環境変数でAPIキー・接続情報を設定するもの

テンプレート

// infrastructure/xxx/index.ts
import { XxxClient } from 'xxx-sdk';

let _client: XxxClient | null = null;

export function getXxxClient(): XxxClient {
  if (!_client) {
    const apiKey = process.env.XXX_API_KEY;
    if (!apiKey) {
      throw new Error('XXX_API_KEY is not defined');
    }
    _client = new XxxClient({ apiKey });
  }
  return _client;
}

まとめ

ポイント 内容
問題 Next.jsのビルド時にモジュールのトップレベルが評価される
原因 環境変数に依存するクライアントをトップレベルで初期化していた
解決策 遅延初期化パターンで、API実行時にのみ初期化する
本質 「ビルド時にはDBに接続しないのだから、ビルド時に初期化する必要はない」
hirotobeat