Vercel + Next.js で外部DBなしにプロトタイピング:組み込みデータベース6選の比較と選定ガイド

重岡 正 ·  Mon, February 9, 2026

Next.js + Vercel でプロトタイプを作るとき、Neon や Supabase のようなマネージドDBをすぐにセットアップするのはオーバーヘッドに感じることがあります。アイデア検証や社内ツールの初期段階では、アプリケーション内で完結するデータベース(インプロセスDB)で十分なケースが多いです。

この記事では、Vercel のサーバーレス制約を踏まえた上で、PGlitesql.jsbetter-sqlite3libSQLDuckDB WASMlowdb の6つを比較し、シナリオ別の選定指針を提示します。

Vercel のサーバーレス制約を理解する

まず前提として、Vercel のランタイム制約を押さえておく必要があります。

制約Node.js ランタイムEdge ランタイム
ファイルシステム読み取り専用(/tmp のみ書き込み可、最大 500MB)アクセス不可
/tmp の永続性ウォームインスタンス間のみ。コールドスタートで消失なし
ネイティブバイナリ動作する(Linux x64 向けビルド必要)動作しない
WASM対応対応(gzip 後 1〜4MB のサイズ制限あり)
最大実行時間300 秒(Fluid Compute)30 秒

最も重要なポイントは、Vercel のサーバーレス関数にはサーバー側の永続ファイルシステムが存在しないということです。SQLite のようなファイルベースDBは書き込み先が保証されません。複数のインスタンスが起動すると、それぞれ独立した /tmp を持ち、データを共有できません。

つまり、Vercel でインプロセスDBを活用する場合、永続化の境界は**ブラウザ(クライアント側)外部ストレージ(S3 など)**のどちらかになります。

永続化パターンの整理

Vercel 上でのインプロセスDB運用には、大きく3つのパターンがあります。

flowchart TD
    A["DB が必要"] --> B{"永続書き込みが必要?"}
    B -->|Yes| C{"保存先は?"}
    C -->|"端末内"| D["IndexedDB / OPFS"]
    C -->|"共有"| E["S3 / R2 同期"]
    B -->|No| F{"実行環境は?"}
    F -->|Browser| D
    F -->|Node| G["/tmp, memory DB"]
    F -->|Edge| H["メモリのみ"]

パターン1:ブラウザ内永続化(推奨)

データをユーザーの端末内に閉じて管理します。PGlite の IndexedDB モード、DuckDB WASM の OPFS、sql.js の手動エクスポートなどが該当します。サーバーコストゼロでオフライン動作も可能ですが、デバイス間のデータ共有はできません。

パターン2:読み取り専用の同梱DB

.sqlite ファイルをデプロイに含め、サーバー側で参照のみ行います。データ更新は再デプロイで対応。辞書や参照データの配信に適しています。

パターン3:一時DB(デモ・検証用)

/tmp やメモリ上にDBを作成します。コールドスタートで消失することを前提とした、デモやE2Eテスト向けの構成です。

6つのインプロセスDBを比較する

PGlite:ブラウザで PostgreSQL を動かす

PGlite は PostgreSQL 17 を WebAssembly にコンパイルしたもので、gzip 後約 3MB のフットプリントで動作します。トランザクション、CTE、ウィンドウ関数、JSONB、さらには pgvector による類似検索まで、PostgreSQL のフル機能が利用できます。

// app/db/client-db.ts(Client Component から呼ぶ)
import { PGlite } from '@electric-sql/pglite';
 
export async function openDb() {
  const db = new PGlite('idb://my-app-db');
  await db.exec(`
    CREATE TABLE IF NOT EXISTS todo (
      id SERIAL PRIMARY KEY,
      task TEXT NOT NULL,
      done BOOLEAN DEFAULT FALSE
    )
  `);
  return db;
}

Drizzle ORMdrizzle-orm/pglite でファーストクラスサポートしており、型安全なクエリが書けます。React 向けには @electric-sql/pglite-reactuseLiveQuery フックでリアクティブなデータ取得も可能です。

ただし、コールドスタート時の初期化に 500ms〜2秒かかる点と、単一接続しかサポートしない点には注意が必要です。Edge ランタイムではサイズ制限によりほぼ動作しません。

向いているケース: 将来 Neon や Supabase に移行予定のプロトタイプ、ブラウザ完結のローカルファーストアプリ

sql.js:最軽量の SQL エンジン

sql.js は SQLite を Emscripten で WASM 化したもので、約 1MB と最小サイズです。コールドスタートも 50〜200ms と高速で、Vercel 上で最もトラブルが少ない WASM DB です。

// app/db/sqljs.ts
import initSqlJs from 'sql.js';
 
export async function openSqlJs() {
  const SQL = await initSqlJs({
    locateFile: (file) => `https://sql.js.org/dist/${file}`,
  });
  const db = new SQL.Database();
  db.run(`CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)`);
  return db;
}

DB 全体がメモリ上に展開される設計のため、大きなDBには不向きです。永続化は db.export()Uint8Array を取り出し、IndexedDB に手動保存する形になります。Drizzle、Prisma、Kysely のドライバが存在しないため、SQL を直接書く必要があります。

向いているケース: コールドスタート速度を重視するプロトタイプ、同梱 SQLite ファイルの読み取り専用配信

better-sqlite3:ローカル開発最速のネイティブバインディング

better-sqlite3 は Node.js 向けの C++ ネイティブアドオンで、同期 API による圧倒的な速度が特徴です。Drizzle ORM、Prisma、Kysely すべてがファーストクラスでサポートしています。

// app/api/search/route.ts
export const runtime = 'nodejs';
 
import Database from 'better-sqlite3';
 
const db = new Database('data/catalog.sqlite', { readonly: true });
 
export async function GET(req: Request) {
  const q = new URL(req.url).searchParams.get('q') ?? '';
  const rows = db
    .prepare('SELECT * FROM items WHERE name LIKE ? LIMIT 20')
    .all(`%${q}%`);
  return Response.json({ rows });
}

ただし、Vercel へのデプロイでは Linux x64 向けのネイティブバイナリが必要で、ビルド互換性の問題が発生しやすいです。Edge ランタイムやブラウザでは動作しません。書き込みは /tmp に限定され、永続化はできません。

向いているケース: 読み取り専用の同梱DBによる高速検索(辞書、カタログ)、ローカル開発環境

libSQL:プロトタイプから本番への最短パス

Turso が開発する SQLite フォーク libSQL で、@libsql/client を通じて3つの接続モードを提供します。ローカル開発では file:local.db を使い、デプロイ時に libsql://your-db.turso.io に URL を切り替えるだけで本番に移行できます。

// lib/db.ts
import { createClient } from '@libsql/client';
 
export const db = createClient({
  url: process.env.TURSO_DATABASE_URL!,   // ローカル: file:local.db
  authToken: process.env.TURSO_AUTH_TOKEN, // ローカルでは不要
});

2026年2月4日にリリースされた @tursodatabase/vercel-experimental は、Turso の部分同期をVercel Functions で実現します。コールドスタート時にスキーマと頻出ページのみ(128KiB)をダウンロードし、追加ページは /tmp にキャッシュ、書き込みはリモートの Turso に送る仕組みです。

Drizzle ORM の drizzle-orm/libsql と Prisma の @prisma/adapter-libsql の両方がサポートされています。ただし、Edge ランタイムでは file: プロトコルが使えないため、HTTP/WSS 経由のリモート接続が前提となります。

向いているケース: SQLite で始めて Turso に移行する予定のプロジェクト、URL 変更だけでデプロイしたいチーム

DuckDB WASM:ブラウザで分析SQLを実行する

DuckDB WASM は列指向ストレージとベクトル化クエリエンジンを備えた分析特化型DBです。S3 や R2 に置いた Parquet ファイルをブラウザから直接 SQL でクエリできるのが最大の特徴です。

SELECT region, SUM(revenue) as total
FROM 'https://storage.example.com/sales.parquet'
GROUP BY region
ORDER BY total DESC

ブラウザの OPFS を使えば永続化も可能です。ただし、Next.js のサーバーサイドでは動作しません。Web Worker 依存のアーキテクチャが App Router と衝突し、ハングやワーカー終了エラーが発生します。MotherDuck の公式 Vercel 連携もクライアントサイド専用です。

また、2025年9月にサプライチェーン攻撃(CVE-2025-59037)が発生しており、npm パッケージ v1.29.2 が侵害されました。v1.30.0 以降を使用してください。

向いているケース: ブラウザ上の分析ダッシュボード、Parquet/CSV を直接クエリする可視化ツール

lowdb:JSON ファイルによる最小構成

lowdb は JSON をそのままデータストアとして扱うミニマルなライブラリです。ブラウザでは localStorage、Node.js では JSON ファイルをアダプタとして使います。

// app/db/lowdb.ts
import { LowSync } from 'lowdb';
import { LocalStorage } from 'lowdb/browser';
 
type Data = { drafts: { id: string; text: string }[] };
 
export function openLowdb() {
  const adapter = new LocalStorage<Data>('myapp');
  const db = new LowSync<Data>(adapter, { drafts: [] });
  db.read();
  return db;
}

SQL の知識不要で最短導入できますが、インデックスや結合、複雑なクエリは期待できません。Vercel のサーバーサイドでは JSON ファイルの永続書き込みができないため、ブラウザ内利用か一時用途に限定されます。Node の cluster もサポートしていません。

向いているケース: 設定値や下書きの保存、SQL 不要な超軽量プロトタイプ

比較表

DBVercel サーバーレスコールドスタートDrizzle ORMVercel 上の永続化サイズSQL 方言
PGlite動作(設定必要)500ms〜2sファーストクラスブラウザ IndexedDB のみ~3MB gzPostgreSQL
sql.js良好50〜200ms非対応手動エクスポート / 読み取り専用同梱~1MBSQLite
better-sqlite3ネイティブバイナリ問題あり~10msファーストクラス読み取り専用同梱のみ~10MB nativeSQLite
libSQLHTTP 経由で良好低〜中ファーストクラスTurso 部分同期で可能可変SQLite + 拡張
DuckDB WASMサーバー側で動作不可1〜5sコミュニティのみクライアント側 OPFS~3.2MB gzDuckDB(PG 風)
lowdb読み取り専用 FS で書き込み不可即時非対応ブラウザ localStorage~10KBJSON / JS 配列

シナリオ別の選定ガイド

将来 PostgreSQL に移行するプロトタイプ → PGlite + Drizzle ORM

ブラウザの IndexedDB 永続化でインフラ設定ゼロで始められます。スキーマとクエリは Neon や Supabase にそのまま移行可能です。コールドスタートが気になる場合はクライアントサイドで動かしましょう。

ローカル開発からデプロイまで最短で → libSQL + Drizzle ORM

file:local.db で開発し、デプロイ時に URL を libsql://your-db.turso.io に変えるだけ。@tursodatabase/vercel-experimental の部分同期を使えば、ローカル速度の読み取りと耐久性のある書き込みが両立します。

ブラウザ完結のクライアントアプリ → Dexie.js / TinyBase

IndexedDB ベースの永続化でサーバー不要です。Dexie.jsuseLiveQuery() によるリアクティブなデータ取得、TinyBase は 3.5〜8.7kB のフットプリントで深いリアクティビティを提供します。フル SQL が必要なら PGlite のブラウザモード(idb://)を選択してください。

読み取り専用の参照データ配信 → sql.js

.sqlite ファイルをデプロイに同梱し、50〜200ms の初期化で軽量にクエリできます。ネイティブバイナリの問題もありません。データ更新は再デプロイで対応します。

分析ダッシュボードの構築 → DuckDB WASM(クライアントサイド)

S3/R2 の Parquet ファイルをブラウザから直接クエリでき、サーバーの計算リソースを消費しません。必ずクライアントサイドで使い、サーバーサイドでの利用は避けてください。

避けるべき選択肢

  • LokiJS:2022年以降メンテナンスされておらず、クエリ結果の不整合やデータ消失のバグがあります。公式サイトは RxDB にリダイレクトされています
  • cr-sqlite:事実上メンテナンスが停止しており、47件のオープンイシューが残っています
  • DuckDB WASM のサーバーサイド利用:App Router との互換性問題が未解決です

まとめ

Vercel のサーバーレス環境における永続化の境界は明確です。関数のファイルシステムは永続ストレージではなく、データはブラウザかクラウドに置く必要があります。

この制約を受け入れた上で、PGlite はブラウザ内での PostgreSQL 体験を、libSQL/Turso は「URL を変えるだけ」のデプロイモデルを提供します。2026年2月4日リリースの Turso 部分同期パッケージは、サーバーレスにリモート書き込みターゲットが必要という現実に正面から向き合った、最もアーキテクチャ的に誠実なソリューションです。

プロトタイプの性質と成長予測に応じて、この記事の選定ガイドを参考に最適な選択を行ってください。

以上、Vercel + Next.js でマネージドDBなしのプロトタイピングに挑んだ、現場からお送りしました。