Next.js × Drizzle ORM:Prismaの呪縛から脱却し、型安全と速度を極める
深夜2時、カフェインで心拍数が跳ね上がる中、ふと自分のコードを眺めて確信した。「俺はPrismaの便利さに甘えすぎていた」と。
開発当初、Prismaは確かに神だった。だが、プロジェクトが肥大化し、エッジ環境へのデプロイ頻度が増すにつれ、その代償が無視できなくなった。巨大なクライアントライブラリがバンドルサイズを膨らませ、コールドスタートの遅延がユーザー体験を削り取る。
「楽をすること」と「コードの品質」は時に背反する。この境界線を再定義したのが、Drizzle ORMへの移行だった。
1. なぜPrismaからDrizzleへ乗り換えるべきか
最大の理由は「実行時のオーバーヘッド」だ。Prismaは強力な抽象化レイヤーを持つが、それが裏目に出て、エッジ環境では以下の問題が顕著になる。
| 項目 | Prisma | Drizzle 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に飛び込め。深夜のコーヒーが、昨日よりも少しだけ美味く感じられるはずだ。