Next.js から HonoX へ。Worker を軽量化するための技術選定
こんにちは!システムデザインワークスの山岸です。
先日公開した 自社サイトを Vercel から Cloudflare Workers へフル移行した記録 の「業務アプリ版」です。ある住宅会社様向けに社内開発している顧客管理アプリを題材に、「小規模な業務アプリに Next.js はやや過剰なのではないか」 という仮説を、実際に移行して確かめました。
結論から言うと、Next.js + Prisma で組んでいたアプリを HonoX + Drizzle + Cloudflare D1 へ移行したところ、Cloudflare Workers にデプロイされるコード(バンドル)が 4.98MB から約 300KB、およそ 16 分の 1 になりました。本記事では、その判断軸と移行を通じて得た知見を共有します。
背景・課題:Worker では「フレームワークの重さ」がそのまま効く
題材は、顧客台帳と定期点検スケジュール(お引渡しから 1 か月〜10 年の 7 段階)を管理する社内向けアプリです。一覧・検索・詳細・CSV 入出力が中心で、機能はさほど複雑ではありません。当初は自社サイトと同じく Next.js + Prisma(Prisma はデータベース操作ライブラリ)で構築し、OpenNext(Next.js を Workers 向けに変換するアダプタ)経由で Cloudflare Workers 上に載せていました。
ここで引っかかりがありました。Workers はリクエストごとにコードを起動するエッジ(世界中に分散したサーバー網)の実行環境のため、デプロイするコードが大きいほど起動が重くなり、コールドスタート(間隔が空いた後の初回起動)の体感に響きます。コンテンツ表示や画像最適化など Next.js の機能を活かす自社サイトなら見合いますが、今回のアプリは突き詰めれば「DB を読み書きして HTML を返す」だけ。多機能さは明らかにオーバースペックでした。そこで弊社のスタイル——まず自分たちで試してから提案する——に沿い、軽いスタックへ寄せれば Worker の性能を引き出せるかを検証することにしました。
取り組みの概要:Workers のために設計された道具に揃える
ゴールは「Worker 上で動くコードを可能な限り小さくする」こと。フレームワーク・ORM・データベースをまとめて見直しました。
フレームワーク: Next.js → HonoX。Hono(エッジ環境向けの軽量 Web フレームワーク)に、Vite やファイルベースのルーティング、操作が必要な部品だけをブラウザで動かす仕組み(後述)を備えたもの
UI: React → hono/jsx。書き味は React に近く、ランタイムははるかに軽量。ページの大半はサーバーで HTML を生成し JavaScript をほぼ送りません
ORM: Prisma → Drizzle ORM。SQL に近い薄い記述で、バンドルが劇的に軽くなります
DB: Cloudflare D1(SQLite ベースのマネージド DB)を継続利用
方針は「無理に動かす」のではなく「最初から Workers 向けの道具に揃える」こと。自社サイト移行で得た学びの延長線上にある判断です。
実装・検証の要点:移行で得た 3 つの知見
「軽くする」という目的のもと、各レイヤーで Workers 流の作法に合わせていきました。その過程で得た、特に再現性の高い 3 つの知見を紹介します。
1. Prisma → Drizzle でバンドルが 4.98MB → 約 300KB に。 Prisma は便利な反面、Workers ではクエリエンジンを WASM(Workers でも動くバイナリ形式)として同梱する必要があり、これがバンドルを押し上げていました。Drizzle は重いエンジンを持たない薄いライブラリで、移行後は約 300KB まで縮小。スキーマ定義を 1 ファイルに集約し、そこからマイグレーション(DB の変更手順)を生成する運用にしています。
1// app/db/schema.ts(抜粋):スキーマを唯一の正本にする2export const customers = sqliteTable("customers", {3 id: integer("id").primaryKey({ autoIncrement: true }),4 customerId: text("customerId").notNull().unique(),5 name: text("name").notNull().default(""),6 assigneeId: integer("assigneeId").references(() => users.id, {7 onDelete: "set null",8 }),9 // ...10});
2. React をやめ、操作が必要な部品だけをブラウザで動かす。 このアプリで操作(クリックでの切り替えなど)が要るのは、点検チェックリストの編集くらいです。HonoX には islands(アイランド) という仕組みがあり、ページ全体は静的な HTML として配信しつつ、その中の「操作が必要な部品」だけを海に浮かぶ島のように切り出してブラウザ側で動かせます。一覧や詳細ページは HTML を返すだけにし、チェックリストだけをこの方式にして「楽観的更新」(操作を即座に画面へ反映し、保存は裏で行う)も React なしで実装しました。ページの大半に JavaScript を送らずに済むため、操作が要る最小限だけを動的にする——これが軽さと使い勝手を両立させる鍵でした。
3. D1(SQLite)の「型の現実」に合わせる。 D1 の実体は SQLite で、日時は文字列、真偽値は 0 と 1 の数値で保存されます。Postgres の感覚で書くと日付の比較や表示で詰まるため、DB には ISO 形式の文字列で保存し、アプリの境界で Date 型と相互変換する方針に統一しました。
なお「Next.js のまま OpenNext で最適化を頑張る」道も比較しましたが、小規模な CRUD アプリでは多機能さが余分なコードとして乗り続けます。目的が Worker の軽量化である以上、土台ごと替えるほうが筋が良いと判断し、Next.js / OpenNext は完全に撤去しました。
結果:仮説どおり、小規模アプリには軽量スタックが効く
当初の仮説「小規模な業務アプリに Next.js はやや過剰」は、このアプリに関しては正しいと確認できました。
比較項目 | 移行前(Next.js + Prisma) | 移行後(HonoX + Drizzle) |
|---|---|---|
Worker バンドルサイズ | 約 4.98MB | 約 300KB |
UI ランタイム | React | hono/jsx(島は最小限) |
ORM | Prisma(WASM エンジン同梱) | Drizzle(薄い SQL ビルダ) |
Next.js を動かす変換層 | OpenNext が必要 | 不要(撤去) |
起動時の重さ(コールドスタート) | バンドルが大きく不利 | 大幅に軽量化 |
※ 2026 年 6 月時点。社内開発アプリでの弊社の検証結果に基づきます。
バンドルが約 16 分の 1 になり、Worker の起動は明確に軽くなりました。ただしこれは Next.js を否定する話ではありません。コンテンツ表示やページ再生成が活きる場面では今も有力な選択肢で、論点はあくまで 「アプリの規模・性質に道具が見合っているか」。今回の小規模業務アプリには軽量スタックが噛み合った、ということです。
まとめ:移行検証から得た 3 つの学び
フレームワークは「規模」で選ぶ: 多機能なフレームワークはその機能を使い切る規模でこそ活きる。小さな業務アプリでは軽量スタックが噛み合うことがある
エッジでは「バンドル予算」が性能に直結する: デプロイするコードの大きさがそのまま起動の重さになる。ORM やライブラリは「サイズ」の観点でも評価したい
プラットフォームの「型の現実」を最初に決める: D1(SQLite)は日時や真偽値の扱いが Postgres と異なる。境界での変換ルールを最初に統一しておくと、後続のロジックがきれいに書ける
ご相談お待ちしております。
「既存の業務システムが重い」「Cloudflare Workers で内製したい」「技術スタックが今の規模に見合っているか見直したい」——こうしたお悩みがありましたら、ぜひ一度ご相談ください。
システムデザインワークスでは、自社での実践・検証に基づき、技術選定からアプリの設計・開発・運用改善まで一貫してサポートしております。栃木県でアプリ開発・システム開発の会社をお探しの方は、お気軽にお問い合わせフォームよりご連絡ください。