Next.jsで16,422件の人物DBと20,000件規模の作品DBを扱うサイトをCloudflare Pagesへ配備した。顔照合はONNX Runtime Webを使い、ブラウザ内で実行する。
実装中に別々に見えた3つの制約が、実際には配信方式を通じて相互に影響した。
- 1ファイルのサイズ
- 1デプロイのファイル数
- Workerのバンドルサイズ
この記事では、2026年8月の無料プラン環境で採用した構成と、なぜ単純な全SSG/全SSRを選ばなかったかを説明する。
Cloudflareは2026年8月現在、新規のNext.jsアプリでは vinext を推奨している。本稿は、すでに @cloudflare/next-on-pages で動いていた既存サイトを制約内へ収めた記録であり、新規案件で同じアダプターを選ぶことを推奨するものではない。
検証環境はNext.js 16.2.4、React 19.2.4、@cloudflare/next-on-pages 1.13.16。
問題1: ONNX Runtime WebのWASMが大きい
ビルド成果物には ort-wasm-simd-threaded.jsep.wasm が入り、1ファイル上限25MiBを超えた。
ただし実装では、WASMの読み込み先をCDNへ明示していた。
const ort = await import('onnxruntime-web');
ort.env.wasm.numThreads = 1;
ort.env.wasm.wasmPaths =
`https://cdn.jsdelivr.net/npm/onnxruntime-web@${ort.env.versions.web}/dist/`;
ビルド成果物側のWASMは実行時に使われない重複物だった。そこでビルド後に24MiBを超えるファイルを走査し、配備対象から除外した。
const LIMIT = 24 * 1024 * 1024;
if (statSync(file).size > LIMIT) {
unlinkSync(file);
}
ポイントは「大きいから削除」ではなく、実行時にどこからロードされるかを先に固定したことだ。ローカルWASMを使う構成なら、この削除は実行時障害になる。
問題2: すべて静的生成するとファイル数が増える
人物詳細をすべて静的生成すると、HTMLだけでなくRSCなど複数ファイルがURLごとに生成される。16,422 URLをそのままSSGすると、Freeプランの1デプロイ20,000ファイル上限に収まらなかった。
最初は生成件数を8,000、100、1,500と変えたが、これは本質的な解決ではない。未生成URLが404になるか、サイトマップと公開ページが食い違うからだ。
そこで大量の詳細ページをSSRへ移した。
/actress/[id]/item/[id]
一方、検索、ランキング、ガイド、かな索引などURL数が限定されたページは静的生成のままにした。
問題3: すべてSSRにするとWorkerが大きくなる
大量ページを動的にしたあと、今度は静的ページまでEdge Runtimeへ寄せるとWorkerのバンドルサイズが増えた。Workers Freeの圧縮後Workerサイズ上限は3MBだった。
静的ページで使うJSONは、ビルド時にローカルファイルから読む。
import fs from 'fs';
import path from 'path';
const DATA_DIR = path.join(process.cwd(), 'public', 'data');
export function readData(...segments) {
return JSON.parse(
fs.readFileSync(path.join(DATA_DIR, ...segments), 'utf8')
);
}
これにより、静的化できるページはWorkerへデータ読み込みコードを持ち込まない。
_routes.json をビルド後に限定する
next-on-pages が生成した _routes.json は、当時の構成では全URLをWorkerへ通す設定になった。大きなモデルやベクトルもWorkerを経由すると、不要な呼び出しが増える。
動的処理が必要なURLだけに限定した。
{
"version": 1,
"include": [
"/actress/*",
"/item/*",
"/api/*"
],
"exclude": []
}
このJSONはビルド後スクリプトで毎回書き換え、手作業による反映漏れを防いだ。
最終的な分担
| 対象 | 配信方法 | 理由 |
|---|---|---|
| 人物・作品の詳細 | SSR | URL数が多く、全SSGではファイル数が増える |
| 検索・ランキング・ガイド | 静的 | URL数が限定され、Workerを使う必要がない |
| JSON・ベクトル・モデル | 静的アセット | 大容量データをWorkerへ載せない |
| 顔照合 | ブラウザ | 利用者画像をサーバーへ送らない |
| ONNX RuntimeのWASM | CDN | 重複する大容量WASMを配備物から外す |
ビルド後に確認すること
デプロイ成功だけではなく、次をsmoke testへ入れた。
- 主要URLが200を返す
- 動的な人物・作品URLが開く
- ベクトル件数とメタデータ件数が一致する
- ベクトルが正規化されている
- 日本語名、読み仮名、ローマ字、誤字の検索例が通る
- サイトマップに公開対象だけが載る
_routes.json の内容と25MiB超ファイルの不在も、デプロイ前にread-backする。
まとめ
今回の構成は、単一の最適化ではなく3制約の折衷だった。
- 大量URLだけ動的にする
- それ以外は静的にする
- 大きなデータは静的アセットとして配る
- ブラウザ推論用の実行ファイルは読み込み先を明示する
- 生成物をビルド後に検査・補正する
DBサイトでは、ページ数、バンドル、モデルサイズを別々に考えると設計が行き詰まる。URL、実行環境、データ転送を一つの配信設計として決める必要があった。
公式資料
この構成は、ブラウザ内顔検索を行うAI AV MATCH(18歳以上向け)の開発で使用した。