メインコンテンツへスキップ
kt-tech.blog

【トラブルシューティング】Prisma v7 の engineType="client" でローカル/Workers 環境を両立させる

トラブルシューティング6分で読めます

この記事でわかること

  • Prisma v7 で engineType の既定が client になり、new PrismaClient() だけでは落ちるという破壊的変更
  • DATABASE_URL のプロトコルで接続方法を分岐させるパターン
  • ローカル用アダプタを静的 import すると Workers 向けビルドが壊れる理由と、その回避策
  • seed.ts だけは同じハックが不要な理由

Prisma v7 以上、@prisma/adapter-pg、Cloudflare Workers(OpenNext)でのデプロイ環境。

メモ
Prisma v7 でデフォルトになった engineType="client" により new PrismaClient() がそのままでは動かなくなります。ローカル開発(PostgreSQL 直接接続)と本番(Cloudflare Workers + Prisma Postgres)を 1 つのコードベースで両立させる方法を解説します。

はじめに

Prisma v7 では エンジンタイプのデフォルトが "client" に変更 されました。このエンジンタイプでは PrismaClient のコンストラクタに accelerateUrladapter のいずれかを渡す必要があり、従来の new PrismaClient() だけではエラーになります。

さらに厄介なのが、ローカル開発用のアダプター(@prisma/adapter-pg)を静的に import すると、Cloudflare Workers 向けのビルドが壊れるという問題です。


1. エラーの内容

Prisma v7 にアップグレード後、以下のエラーが発生します。

PrismaClientConstructorValidationError:
Using engine type "client" requires either "adapter" or "accelerateUrl"
to be provided to PrismaClient constructor.

これは以下の問題を連鎖的に引き起こします。

  • ローカル DB 接続不可(accelerateUrl は Prisma Postgres 用)
  • seed.ts の失敗(独自の PrismaClient インスタンス)
  • Workers ビルドエラー(@prisma/adapter-pg の静的 import)

2. ランタイム分岐パターン

DATABASE_URL のプロトコルで使用する初期化方法を切り替えます。

TypeScript
import { PrismaClient } from "@prisma/client";

function createPrismaClient(): PrismaClient {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) {
    throw new Error("DATABASE_URL environment variable is not set");
  }

  // Workers: Prisma Postgres 経由(HTTP)
  if (databaseUrl.startsWith("prisma+postgres://")) {
    return new PrismaClient({ accelerateUrl: databaseUrl });
  }

  // ローカル: @prisma/adapter-pg で TCP 直接接続
  const moduleName = "@prisma/adapter-pg";
  // eslint-disable-next-line no-eval
  const { PrismaPg } = eval(`require("${moduleName}")`);
  const adapter = new PrismaPg({ connectionString: databaseUrl });
  return new PrismaClient({ adapter });
}
Tips
prisma+postgres:// → Prisma Postgres(HTTP 経由)、postgresql:// → ローカル PostgreSQL(TCP 直接接続)。URL の形式だけで切り替えるのが最もシンプルです。

3. なぜ eval(require()) なのか

通常の importrequire() では esbuild が静的解析でモジュールをバンドルに含めようとします。

import { PrismaPg } from "@prisma/adapter-pg"
  ↓ esbuild が @prisma/adapter-pg を解析
  ↓ pg パッケージに依存
  ↓ pg が pg-cloudflare を require
  ↓ Workers 向けビルドで pg-cloudflare が解決できずエラー

try-catch で囲んだ require() でも esbuild は解析を行います。eval() で文字列として require を呼ぶことで、esbuild の静的解析を完全に回避できます。

Tips
Workers 環境では DATABASE_URLprisma+postgres:// なので、eval(require()) のコードパスには到達しません。ランタイムで問題が起きることはありません。
ポイント
これは最後の手です。バンドラーの設定を自分でいじれるなら、対象パッケージを external に指定する方が素直です(esbuild なら external: ["@prisma/adapter-pg"])。OpenNext のようにビルド工程が隠されていて設定を差し込みにくいときの逃げ道として読んでください。

4. seed.ts への適用

seed.ts も独自の PrismaClient を持つため、同様のパターンを適用します。

TypeScript
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const databaseUrl = process.env.DATABASE_URL!;
const prisma = databaseUrl.startsWith("prisma+postgres://")
  ? new PrismaClient({ accelerateUrl: databaseUrl })
  : new PrismaClient({
      adapter: new PrismaPg({ connectionString: databaseUrl }),
    });
Tips
seed.tsbun で直接実行されるため、eval(require()) は不要です。esbuild を経由しないので、通常の import で問題ありません。

5. ローカル DB の初期化手順

Bash
# DB リセット(pgvector 拡張の再作成が必要)
bunx prisma db push --force-reset

# pgvector 拡張を再作成
PGPASSWORD=postgres psql -h localhost -p 5434 -U postgres -d anoni \
  -c 'CREATE EXTENSION IF NOT EXISTS vector;'

# スキーマ反映 + シード
bunx prisma db push --accept-data-loss
bunx prisma db seed

まとめ

  • Prisma v7 の engineType="client" は breaking change。accelerateUrladapter が必須に
  • DATABASE_URL のプロトコルでランタイム分岐するのが最もシンプル
  • eval(require()) はハックだが、Workers ビルドとローカル開発を両立させる実用的な手法
  • seed.ts は bun 直接実行なので通常の import で OK
  • ローカル DB リセット時は pgvector 拡張の再作成を忘れずに

参考リンク

更新履歴

  1. eval(require()) が最後の手であり、バンドラー設定をいじれるなら external 指定の方が素直であることを補足。コードブロックの言語指定を plain text から text に修正し、ファイルパスのキャプションを追加。callout の色指定を他記事と揃えた。参考リンクの節を新設し、Prisma 周りの関連記事へのリンクを追加。「この記事でわかること」「対象読者」「前提条件」を概要プロパティへ移動。

この記事のタグ