メインコンテンツへスキップ
← 記事一覧に戻る
·開発·9 min read

Next.js 本番だけ ERR_INVALID_CHAR になった全記録 — 環境変数末尾改行を Sentry で掴むまで3日

Next.jsデバッグSentry個人開発Vercel
Next.js 本番だけ ERR_INVALID_CHAR になった全記録 — 環境変数末尾改行を Sentry で掴むまで3日

結論: 本番だけ再現するエラーは、環境変数の「見えない文字」を疑え

Next.js 16 + Vercel 構成で ERR_INVALID_CHAR本番だけ 出て、ローカルでは一切再現しない。このパターンの犯人の一つが、環境変数の末尾に紛れ込んだ改行文字 (\n) です。

Vercel の Dashboard でコピペした環境変数に末尾改行が混入 → それが HTTP の Host ヘッダーにそのまま乗る → Node.js が「ヘッダーに不正文字がある」と判断して ERR_INVALID_CHAR を投げる。ローカルの .env.local はテキストエディタが末尾改行を自動トリムするので再現しない。

この記事は、筆者が masatoman.net(Next.js 16 + Supabase)でこのバグを踏み、Sentry のスタックトレースで発見するまでの3日間の記録です。

masatoman.net は Next.js 16 + Supabase + Vercel で動く個人ブログ。2026年5月末に Sentry を導入済みで、フリープラン(5,000 イベント/月)を使用中。本番デプロイ後に ERR_INVALID_CHAR が散発的に出始め、ローカルでは再現しないためデプロイ差分から3日かけて追った実話。

この記事でわかること:

  • ERR_INVALID_CHAR がなぜ本番だけ発生するか(仕組みの説明)
  • 環境変数の末尾改行がどう HTTP ヘッダーに影響するか
  • Sentry のスタックトレースでどこを見れば1時間以内で特定できるか
  • 予防として env var を trim する実装パターン

この記事の前提

  • スタック: Next.js 16 (App Router) + Supabase + Vercel
  • Sentry: フリープラン導入済み(@sentry/nextjs ウィザード経由)
  • 発生時期: 2026年5月末
  • 再現頻度: 散発的(全リクエストではなく特定のルートで)

「それ俺だ」な詰まり方

こんな状況に心当たりはないでしょうか。

  • ローカルでは完全に動く。 npm run dev で試しても、npm run build && npm run start でも出ない。
  • 本番だけ散発的に 500 が出る。 ランタイムログを見ると ERR_INVALID_CHAR とだけ書いてある。
  • 直前の PR に問題はない(ように見える)。デプロイ差分を1時間追ったが怪しい箇所がない。
  • 再デプロイしても再発する。 キャッシュ問題ではない。

筆者はこの状態で「Node.js のバグか?」「Vercel のエッジ環境の問題か?」と的外れな方向を2日掘りました。


実際に起きたこと

Day 1: 本番 500 の発覚

masatoman.net のある記事ページで 500 エラーが散発。Vercel のランタイムログには以下が出ていました:

Error: Invalid character in header content ["host"]
  code: 'ERR_INVALID_CHAR'

「host ヘッダーに不正文字」。ピンとこなかったので、まずコードを疑いました。直近のコミットを全部レビューしても怪しい箇所なし。

Day 2: ローカル再現を諦める

ローカルで NEXT_PUBLIC_SITE_URL 等の環境変数をいろいろ変えて試しましたが、一切再現しない。next build && next start で本番に近い状態を作っても出ない。

「本番特有の何か」だという確信は持てたものの、何を変えれば直るかわからず。

Day 3: Sentry のスタックトレースで発見

Sentry のエラー詳細画面を開いて初めて気づきました。エラーの extra フィールドに、問題の Host ヘッダー値がそのまま記録されていました:

host: "masatoman.net\n"

末尾に \n がある。

Vercel の Environment Variables 画面を開いて、NEXT_PUBLIC_SITE_URL の値を確認すると一見正常に見えました。しかしこの値を コピーしてメモ帳に貼ると末尾に改行が入っていた。Vercel の入力フォームで値をコピペした際、元のソース(ターミナルの echo 出力など)に末尾改行が含まれていたのです。


原因分析

Node.js の HTTP モジュールは、ヘッダー値に \r\n\0 が含まれると ERR_INVALID_CHAR を投げます(HTTP インジェクション防止の仕様)。

// Node.js の内部チェック(概念)
if (/[\r\n\0]/.test(value)) {
  throw new ERR_INVALID_CHAR('header content');
}

ローカルで再現しない理由:

環境env var の読み込み方末尾改行の扱い
ローカル (.env.local)dotenv (Next.js 内蔵)自動トリム
Vercel Dashboard生文字列をそのまま保存トリムしない

Vercel は入力値を忠実に保存します。末尾改行があればそのまま環境変数になり、それが fetch()new URL() のホスト解決に使われ、最終的に HTTP ヘッダーに乗ります。


判断基準 — Sentry のどこを見るか

Sentry の extra にホスト値が残っていなければ、「ERR_INVALID_CHAR = ヘッダーに改行」という接続は自力では難しかったと思います。Vercel のランタイムログだけでは値の中身までは出ません。

Sentry で確認すべき3箇所:

  1. エラーの extra フィールド → Node.js が記録した変数ダンプ。Host ヘッダー値がそのまま出ていることがある
  2. breadcrumbs → 直前のネットワークリクエストのヘッダー一覧
  3. スタックトレースの node:_http_outgoing.js → ヘッダー書き込み失敗の行。ここで止まっていたら env var 由来が濃厚

修正と予防

即時修正

Vercel Dashboard の Environment Variables から対象の値を 削除して再入力。コピペではなくキーボードで手打ちするか、入力後に値末尾にカーソルを置いて余分な文字がないか確認。

コードで防ぐ(推奨)

env var を使う前に .trim() を噛ませておくと、同じ事故が再発しても影響を防げます:

// lib/config.ts
export const SITE_URL = (process.env.NEXT_PUBLIC_SITE_URL ?? '').trim();
export const SUPABASE_URL = (process.env.NEXT_PUBLIC_SUPABASE_URL ?? '').trim();

fetch() の URL 生成、new URL() への渡し先、ヘッダーへの直接参照 — すべてこの定数経由にすれば、修正が一点になります。

デプロイ前の確認

# Vercel CLI で環境変数を pull して確認
vercel env pull .env.vercel.local
cat -A .env.vercel.local | grep 'SITE_URL'
# 末尾が「...net$」ならOK。「...net^M$」や行が2本なら改行あり

cat -A は改行を $ で、CR (\r) を ^M で表示します。


で、どう稼ぐ?

個人開発でこのようなバグに3日取られると、開発→リリース→収益化のサイクルが大きく遅れます。Sentry を入れていなければ、今回の犯人特定にさらに時間がかかっていた(あるいは諦めていた)可能性が高い。

フリープランで十分。筆者の masatoman.net では月のエラーイベント消化は無料枠(5,000 件)の数パーセント程度で収まっています。個人開発の副業収益を守りたいなら、Sentry は「コストゼロで使える保険」として今すぐ入れておく価値があります。


今日やること(3 つ)

  1. Sentry を入れていない場合は今すぐ入れる — フリープランで十分。npx @sentry/wizard@latest -i nextjs で10分。
  2. env var を使っている箇所を .trim() でラップするlib/config.ts に集約して1行追加するだけ。
  3. Vercel の env var を cat -A で一度見直す — 末尾改行の有無を確認し、疑わしい値は削除→再入力。

Next Step

次に読むならこの導線です

すべての記事を見る
有料で次へ進む¥1,000

【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード

Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。

次の実験記録も追う

Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

この記事が役に立ったらシェア