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箇所:
- エラーの
extraフィールド → Node.js が記録した変数ダンプ。Host ヘッダー値がそのまま出ていることがある breadcrumbs→ 直前のネットワークリクエストのヘッダー一覧- スタックトレースの
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 つ)
- Sentry を入れていない場合は今すぐ入れる — フリープランで十分。
npx @sentry/wizard@latest -i nextjsで10分。 - env var を使っている箇所を
.trim()でラップする —lib/config.tsに集約して1行追加するだけ。 - Vercel の env var を
cat -Aで一度見直す — 末尾改行の有無を確認し、疑わしい値は削除→再入力。
Next Step
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
Claude Code で 0→MVP を1日で作る全記録 — recipe-ai Build in Public
Claude Codeを使い、YouTube料理動画からレシピを自動抽出するAIアプリ「recipe-ai」を0からMVPまで1日で構築した全記録。CLAUDE.md設計、API実装、Supabase連携、Vercelデプロイ、Stripe課金導入までの工程を時系列で公開。
【第1回】Claude Code Skills 入門 — 自作スキルで開発効率を2倍にする実装ガイド
Claude Code の Skills 機能を自作する手順を、masatoman.net 周辺の自動化を Claude Code で回している立場で実コード付きで解説。1 スキル 15 分の投資で月 10 時間の作業を削減する実装ガイドです。
次の実験記録も追う
Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
この記事が役に立ったらシェア