Next.js × Drizzle ORM:Prismaの呪縛から脱却し、型安全と速度を極める

Next.js × Drizzle ORM:Prismaの呪縛から脱却し、型安全と速度を極める

深夜2時、カフェインで心拍数が跳ね上がる中、ふと自分のコードを眺めて確信した。「俺はPrismaの便利さに甘えすぎていた」と。

開発当初、Prismaは確かに神だった。だが、プロジェクトが肥大化し、エッジ環境へのデプロイ頻度が増すにつれ、その代償が無視できなくなった。巨大なクライアントライブラリがバンドルサイズを膨らませ、コールドスタートの遅延がユーザー体験を削り取る。

「楽をすること」と「コードの品質」は時に背反する。この境界線を再定義したのが、Drizzle ORMへの移行だった。


1. なぜPrismaからDrizzleへ乗り換えるべきか

最大の理由は「実行時のオーバーヘッド」だ。Prismaは強力な抽象化レイヤーを持つが、それが裏目に出て、エッジ環境では以下の問題が顕著になる。

項目PrismaDrizzle ORM
バンドルサイズ数MB単位(巨大)数十KB単位(軽量)
コールドスタート遅い(ライブラリ初期化負荷)極めて高速
SQL生成ブラックボックス記述したクエリがそのままSQLに
型安全性スキーマ依存(生成コスト大)TypeScriptの型推論をフル活用

Prismaはクエリを解釈するエンジンがランタイムに含まれるため、サーバーレス環境での起動速度に直結する。対してDrizzleは、TypeScriptの型をSQLの記述にマッピングする薄いラッパーに過ぎない。この「薄さ」こそが、現代のフルスタック開発において最強の武器になる。


2. Server Actionsで「堅牢な」DB操作を実装する

Server ActionsでDBを叩く際、バリデーションを疎かにするのは自爆行為に等しい。Zodと組み合わせることで、型安全かつ堅牢なデータ処理を担保するのがプロの流儀だ。

"use server";

import { db } from "@/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
import { z } from "zod";
import { revalidateTag } from "next/cache";

const UpdateUserSchema = z.object({
  id: z.number().int().positive(),
  name: z.string().min(2).max(50),
});

export async function updateUserProfile(data: unknown) {
  // 1. バリデーション
  const parsed = UpdateUserSchema.safeParse(data);
  if (!parsed.success) throw new Error("不正な入力データです");

  const { id, name } = parsed.data;

  // 2. DB操作
  const result = await db
    .update(users)
    .set({ name })
    .where(eq(users.id, id))
    .returning();

  // 3. キャッシュの無効化(重要!)
  revalidateTag(`user-${id}`);

  return result[0];
}


3. 事故を防ぐディレクトリ構成と `server-only`

クライアントコンポーネントから誤ってDB接続を呼び出すと、環境変数がブラウザに露呈する。これを防ぐには、`server-only`パッケージを用いた強制的な分離が不可欠だ。

ディレクトリ構成例:

src/
├── db/
│   ├── index.ts      // Drizzleのクライアント生成
│   └── schema.ts     // テーブル定義
├── lib/
│   └── db-client.ts  // server-onlyをインポートするガード
└── app/

`src/lib/db-client.ts`:

import 'server-only';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const queryClient = postgres(process.env.DATABASE_URL!);
export const db = drizzle(queryClient);

このように「サーバー専用」であることをコードレベルで強制すれば、ビルド時にエラーを吐いてくれる。この「物理的な防壁」こそが、深夜のコーディングにおける最大の安心材料だ。


4. キャッシュ戦略:更新漏れを撲滅する

Next.jsの`unstable_cache`は強力だが、タグ管理をミスると「DBは更新されたのにUIが変わらない」という地獄を見る。ベストプラクティスは、「タグの粒度を細かくし、Server Actionsで確実にパージする」ことだ。

import { unstable_cache } from "next/cache";
import { db } from "@/db";

export const getCachedUser = async (id: number) => {
  return unstable_cache(
    async () => db.query.users.findFirst({ where: eq(users.id, id) }),
    [`user-${id}`], // 一意なキー
    { tags: [`user-${id}`], revalidate: 3600 }
  )();
};

このように、データ取得単位でタグを生成し、Server Actions側で`revalidateTag("user-" + id)`を叩く。この運用を徹底することで、キャッシュの一貫性を保ちつつ、DBへの負荷を劇的に下げることができる。


最後に:型安全は「麻薬」である

Drizzleへの移行は、最初は面倒だ。SQLライクな記述に慣れるまで時間はかかるし、マイグレーションの手順もPrismaとは異なる。

しかし、一度この環境に慣れてほしい。ビルドサイズが小さくなり、クエリが高速化し、何より「型が合わない」という理由でデプロイが落ちる悲劇が激減する。

バックエンドエンジニアとして、この「堅牢さ」は麻薬だ。もし今、Next.jsのパフォーマンスと開発体験にジレンマを感じているなら、迷わずDrizzleに飛び込め。深夜のコーヒーが、昨日よりも少しだけ美味く感じられるはずだ。