0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js + Cloudflare Pagesで16,422件のDBサイトを公開するまでに踏んだ3つの制約

0
Posted at

Next.jsで16,422件の人物DBと20,000件規模の作品DBを扱うサイトをCloudflare Pagesへ配備した。顔照合はONNX Runtime Webを使い、ブラウザ内で実行する。

実装中に別々に見えた3つの制約が、実際には配信方式を通じて相互に影響した。

  1. 1ファイルのサイズ
  2. 1デプロイのファイル数
  3. 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歳以上向け)の開発で使用した。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?