マキモノ
Web開発無料✅ 公式検証済みv1.0.0 / 更新

「エラー番号しか出ない」Next.js 本番不具合を、本番を壊さずに原因まで追い詰める

Next.js の error digest だけが出る本番障害を、書き込みゼロの再現・スキーマ読み取り・hidden input required の罠の3点で特定し、Server Action から throw を無くして二度と番号だけにしない手順

出品者: kim@orgiast.jp📖 読込 約3,072トークン (約5円)💰 コスパ 18
トークン節約メーター95%節約
ゼロからAIに作らせた場合6万トークン
このMDを読ませた場合3,200トークン

5.7万トークンの節約 (API料金換算で約85円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

「エラー番号しか出ない」Next.js 本番不具合を、本番を壊さずに原因まで追い詰める」は、Web開発カテゴリのAI指示書(MDファイル)です。Next.js の error digest だけが出る本番障害を、書き込みゼロの再現・スキーマ読み取り・hidden input required の罠の3点で特定し、Server Action から throw を無くして二度と番号だけにしない手順この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約5.7万トークン(API料金換算で約85円)・95%のトークンを節約できます。

カテゴリ
Web開発
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約6万トークン
この巻物使用時
約3,200トークン
節約量
約5.7万トークン (約85円)
更新日
2026-09-17

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/next-js-2/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

「エラー番号しか出ない」Next.js 本番不具合を、本番を壊さずに原因まで追い詰める

対象: Next.js App Router(Server Actions / error.tsx)を Vercel 本番で運用していて、利用者から「エラー識別子: 1234567890 で登録できない」とだけ報告が来た状況。AI エージェントにそのまま渡して調査させる前提で書いてある。

0. 前提として知っておくこと(これを知らないと調査が空回りする)

  • digest は stringHash(err.message + err.stack)。実装は next/dist/server/app-render/create-error-handler.js。stack は本番では minify 済みなので、digest から元のメッセージを逆算することはできない
  • vercel logs <domain> は「今から先」の tail であって過去ログの検索ではない。報告された digest のログは、報告から時間が経っていればもう取れない
  • したがって最短経路は「ログを探す」ではなく 「digest が出ない形にコードを直す」。直せば次の1回で日本語の原因が画面に出る。

1. まず切り分ける: ページ描画で落ちているのか、送信で落ちているのか

書き込みを一切起こさずに確認できる。Playwright で本番にログインして GET するだけのテストを1本書く。

// e2e/repro.spec.ts  ※ storageState でログイン済みコンテキストを使う
import { test } from "@playwright/test";
const BASE = process.env.E2E_BASE_URL ?? "https://<本番ドメイン>";

for (const path of ["/dashboard", "/<問題のページ>"]) {
  test(`GET ${path}`, async ({ page }) => {
    const resp = await page.goto(BASE + path, { waitUntil: "domcontentloaded" });
    const body = await page.locator("body").innerText();
    const m = body.match(/エラー識別子:\s*(\d+)/);   // error.tsx の文言に合わせる
    console.log(path, resp?.status(), m ? m[1] : "no-digest");
    console.log(body.slice(0, 400));
  });
}
  • ここで digest が出れば ページ描画の問題。出なければ 送信(Server Action)の問題で、以下へ進む。
  • 本番へのフォーム実送信は「本番データの書き込み」なので、安全機構に止められることがある(止められるのが正しい)。止められた前提で進められる手順を以下に用意している。

2. スキーマ側の可能性を、読み取りだけで潰す

Server Action の INSERT が落ちている可能性は、本番DBを読むだけで大半を否定できる。PostgreSQL なら:

-- NOT NULL かつ default 無しの列(INSERT ペイロードに無ければ必ず失敗する)
select column_name, data_type from information_schema.columns
 where table_schema='public' and table_name='<table>' and is_nullable='NO' and column_default is null;
-- CHECK / FK
select conname, pg_get_constraintdef(oid) from pg_constraint
 where conrelid='public.<table>'::regclass and contype in ('c','f');
-- RLS(アプリは anon キー+ユーザーセッションで書くので、ここが閉じていれば全件失敗する)
select policyname, cmd, qual, with_check from pg_policies
 where schemaname='public' and tablename='<table>';

ここが全部素直なら、原因は 入力バリデーション(zod 等) 側に寄る。

3. いちばん多い犯人: 「hidden input の required」

検索コンボボックス(可視の text 入力+選択結果を入れる hidden input)でこれが起きる。

// ❌ これは効かない
<input type="hidden" name="customer_id" value={selected?.id ?? ""} required />

HTML 仕様上 hidden input は制約検証(constraint validation)から除外されるため、required を付けてもブラウザは送信を止めない。利用者が「名前を打ったが候補をクリックしていない」状態で送信でき、サーバ側の z.string().uuid() が落ちて digest 画面になる。しかも利用者から見ると「ちゃんと入力したのに登録できない」ので、報告からは原因が分からない。

// ✅ 可視 input 側で止める
const textRef = useRef<HTMLInputElement>(null);
useEffect(() => {
  textRef.current?.setCustomValidity(
    required && !selected ? "候補の中から選んでください(入力しただけでは選択されません)" : "",
  );
}, [required, selected]);

<input type="hidden" name={name} value={selected?.id ?? ""} />
<input ref={textRef} type="text" value={query} onChange={...} />

同種の罠: disabled な入力は送信されない / type="number" はカンマ付き文字列で z.coerce.number() が NaN になり ZodError になる / フォームが送っているのにスキーマが拾っていない項目はエラーも出さずに黙って保存されない

4. 恒久対策: Server Action から throw を無くす

Server Action で throw すると、利用者には digest 番号しか届かない。入力エラーも DB エラーも throw せず、入力値を保持したままフォームへ戻す

function backToForm(raw: Record<string, unknown>, message: string): never {
  const carry = new URLSearchParams({ /* 入力値を全部持ち帰る */ error: message });
  redirect(`/<フォームのパス>?${carry.toString()}`);
}

export async function createX(formData: FormData) {
  const raw = Object.fromEntries(formData.entries()) as Record<string, unknown>;

  // 落ちやすい前提は個別に日本語で弾く(zod に任せると ZodError が digest 化する)
  if (!String(raw.customer_id ?? "").trim()) {
    backToForm(raw, "顧客が選択されていません。候補の中からクリックして選んでください。");
  }

  let parsed: z.infer<typeof Schema>;
  try {
    parsed = Schema.parse(raw);
  } catch (e) {
    console.error(`[createX] zod: ${(e as Error).message}`, { rawKeys: Object.keys(raw) });
    backToForm(raw, `入力エラー: ${(e as Error).message}`);
  }

  const { data, error } = await db.from("<table>").insert({ ...parsed }).select("id").single();
  if (error) {
    console.error(`[createX] insert: ${error.message}`, { code: error.code, details: error.details });
    backToForm(raw, `登録に失敗しました: ${error.message}`);
  }
  redirect(`/<詳細>/${data.id}`);
}

フォーム側で ?error= をバナー表示する。redirect()NEXT_REDIRECT を throw する仕組みなので、try の中で呼ばない(catch に飲まれる)。上の形(catch の中 / try の外で呼ぶ)なら安全。

5. 検証も書き込み無しでできる

// ① バナーが出ること
await page.goto(`${BASE}/<フォーム>?error=${encodeURIComponent("テスト")}`);
// ② 未選択で送信がブラウザに止められること(= レコードは作られない)
await page.locator('form input[type="text"]').first().fill("テスト会社");
await page.locator('form button[type="submit"]').last().click();
const msg = await combo.evaluate((el: HTMLInputElement) => el.validationMessage);
// URL がフォームのままで、validationMessage が期待文言であること

6. 落とし穴: 本番が git のどのブランチから出ているか先に確かめる

vercel inspect <本番ドメイン> に git メタデータが出てこない場合、その本番は CLI(vercel --prod)で作業ツリーからデプロイされている。このとき origin/main を読んで調査すると、本番に無いコードを読んで誤診する(実例: main 側だけ商材が4値の固定 enum のままで、本番は自由文字列だった)。必ず「本番と同じソース」を読むこと。作業ツリーに未コミット差分がある場合は、その差分が本番デプロイ時刻より前か後かを mtime で確認してから再デプロイする。

7. AI に投げるときの指示テンプレ

本番 <URL> の <機能> が Next.js の error digest <番号> で失敗している。
1. 本番へ GET のみの Playwright で <該当ページ> を開き、描画時点で落ちるか送信時に落ちるか切り分けて報告(書き込み禁止)
2. 対象テーブルの NOT NULL/CHECK/RLS を読み取りだけで確認
3. フォームの各 input の name/type と、Server Action のスキーマの項目を突き合わせ、
   (a) hidden input に required を頼っている箇所 (b) フォームが送るのにスキーマが拾っていない項目 を列挙
4. Server Action の throw を ?error= へのリダイレクトに置き換え、console.error で原因をログに残す
5. 書き込み無しの Playwright で「バナーが出る」「未選択で送信が止まる」を実測して報告
推測は書かず、実測できたことと「未取得」を分けて報告すること。

よくある質問

「「エラー番号しか出ない」Next.js 本番不具合を、本番を壊さずに原因まで追い詰める」とは何ですか?

Next.js の error digest だけが出る本番障害を、書き込みゼロの再現・スキーマ読み取り・hidden input required の罠の3点で特定し、Server Action から throw を無くして二度と番号だけにしない手順

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約6万トークンかかりますが、この巻物を使えば約3,200トークンで済みます。差し引き約5.7万トークン(API料金換算で約85円)・95%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア