ガイド 第46

トラブルシューティング

@event/*

3アプリの運用で実際に踏んだ落とし穴のカタログである。各項目は「症状 → 原因 → 対策(実装済みの守り)」で記す。守りはパッケージに焼き込んであるため、SDK / factory を通っている限り再発しない。自前コードで迂回した箇所だけが再発点になる。

1. env の空白 → 401

  • 症状: 正しいはずの API キーで 4S が 401 を返す。目視では env は正しい
  • 原因: env 値の前後空白・末尾スラッシュ(コピペ時の混入)が Authorization ヘッダ / URL を壊す
  • 対策: createFoursConfig が全値を trim()、base URL は normalizeBase() で末尾スラッシュも除去。isCronAuthorized も trim 硬化済み。SDK を通らない手書き fetch だけが危険

2. Blob eventual consistency

  • 症状: 連続 POST の直後に list() すると直前に書いたデータが見えない / 消えたように見える
  • 原因: Vercel Blob の list() は eventual consistent
  • 対策: @event/asset-kit は存在確認を byte-hash キーrehostByHash — 同一バイト列は同一 URL)に寄せ、list() に依存しない。読み戻し検証が必要な書き込みは書いた URL を直接 GET する

3. 凍結バンドル hash → 保存が永久 409

  • 症状: デプロイ後、admin の保存が全て conflict(409)になり、リロードしても直らない
  • 原因: 本番の fs はデプロイ時点で凍結されたバンドル。fs 基準の hash と GitHub HEAD の hash が最初の commit 以降ずっと乖離する
  • 対策: createContentStore.readResourceWithHash は本番では GitHub 最新を読む。さらに書き込み成功時は「今書いた内容」の sha256 を返し、次の保存の基準を確実に一致させる(第14章の不変条件)

4. dev の distDir 共有事故

  • 症状: next dev 起動中に yarn build を打つと dev が壊れる(謎の 500 / モジュール解決エラー)
  • 原因: dev と build が同じ .next を共有し、build が dev のキャッシュ/マニフェストを上書きする
  • 対策: createNextConfigNEXT_DIST_DIR / BUILD_LOCAL=1 で distDir を .next-local に分離する。dev 中の検証は typecheck か BUILD_LOCAL=1 ビルドを使う

5. .next cache 破損 → 全 route 500

  • 症状: dev で突然すべての route が 500。.next/cache を消すと直る
  • 原因: dev の webpack ファイルキャッシュ破損
  • 対策: createNextConfig の webpack hook が dev では cfg.cache = { type: "memory" } を強制。破損しうるファイルキャッシュ自体を持たない

6. Upstash の JSON 自動デシリアライズ

  • 症状: KV に保存した JSON 文字列を JSON.parse すると本番だけ "[object Object]" で落ちる。dev(メモリ shim)では再現しない
  • 原因: Upstash は値が JSON らしいと get / hget / hgetall で自動デシリアライズしたオブジェクトを返す
  • 対策: @event/coreRealKv は string 以外を JSON.stringify し直して返す coercion ガードを全 getter に内蔵。KvLike を通す限り「保存時の文字列」が必ず戻る

7. transform:scale プレビューの直撮りズレ

  • 症状: 書き出した PNG で文字が数 px ズレる / にじむ。画面では正常
  • 原因: 縮小プレビュー(transform: scale)を直接 rasterize するとサブピクセル丸めが焼き込まれる
  • 対策: captureCanvas は画面外に native 寸法・transform: none で mount してから撮る。captureElementCanvas も clone に transform: "none" を強制する

8. serverless コールドスタートで画像が抜ける

  • 症状: 一括書き出しで本番でだけロゴ・顔写真が白抜けになる。1枚ずつなら成功する
  • 原因: 大量ジョブが img-proxy / 画像ホストへ同時 fetch し、serverless のコールドスタートに殺到してタイムアウトする
  • 対策: inlineImages併走上限6 + 失敗リトライ3回(バックオフ付き)で data URI 化し、waitImages で load 完了を待ってから rasterize する。bulkZip 側も worker pool(既定4)で総量を絞る

9. edge bundle への node:* 混入

  • 症状: middleware のビルドが node:fs / node:crypto を解決できず失敗する、または edge サイズ超過
  • 原因: middleware(edge runtime)から @event/corebarrel を import すると、contentStore(node:fs / node:crypto / octokit)まで束で edge bundle に入る
  • 対策: edge から使うものは subpath から import する — @event/core/auth(constantTimeEqual)のみ。@event/shellcreateAuthGateMiddleware は最初からそう書かれている。同型の対策として @event/session-card/persist(server 専用)と @event/asset-kit の 4 entrypoint 分割がある

10. 楽観ロックの 409 は「正常系」

  • 症状: 編集保存で「他のセッションが先に更新しました」
  • 原因: 本物の競合。別タブ / 別担当者が先に保存した
  • 対策: 仕様どおりの動作である。リロードして最新 hash を取得してから再編集する。カード overlay(OverlayStore.patch)は single-record PATCH + 自動リトライ6回なので、同一セッションを同時編集しない限りユーザーには見えない