はじめに
Next.jsアプリケーションをVercelにデプロイしようとしたところ、ローカルでは成功するのにVercelでは失敗するという問題に遭遇しました。エラーメッセージは曖昧で、原因の特定に苦労しました。
この記事では、問題の発見から解決までの過程を共有し、JavaScriptの演算子優先順位について学んだことをまとめます。
問題の症状
エラーメッセージ
Error occurred prerendering page "/en/smells/22-03"
[Error: An error occurred in the Server Components render.
The specific message is omitted in production builds to avoid leaking sensitive details.]
特徴的な現象
- ローカルビルドは成功 するが、Vercelでは失敗
- 毎回異なるページ でエラーが発生(22-03、24-03、25-04など)
- エラーの詳細が本番ビルドでは隠される
原因の発見
複数のファイルを調査する中で、以下のコードパターンを発見しました:
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
一見問題なさそうに見えますが、ここに演算子優先順位の罠 が潜んでいました。
演算子優先順位とは
JavaScriptでは、演算子には優先順位があります。数学で「掛け算は足し算より先に計算する」のと同じです。
優先順位の例
// 数学と同じ
2 + 3 * 4 // → 2 + 12 → 14(3 * 4 が先)
// 文字列連結 (+) と論理和 (||) の場合
// + の優先順位: 13
// || の優先順位: 5
// → + が先に評価される!
問題のコードを分解
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
このコードは以下のように解釈されます:
const origin = process.env.NEXT_PUBLIC_SITE_URL || ('' + process.env.NEXT_PUBLIC_BASE_PATH) || '';
// ↑ここが先に評価される
環境変数が未定義の場合
// NEXT_PUBLIC_SITE_URL = undefined
// NEXT_PUBLIC_BASE_PATH = undefined の場合
const origin = undefined || ('' + undefined) || '';
// ↓
const origin = undefined || 'undefined' || '';
// ↓
const origin = 'undefined'; // 文字列 'undefined' になる!
'' + undefined は文字列 'undefined' になります。これはJavaScriptの型変換の仕様です。
なぜローカルでは成功したのか
ローカル環境では環境変数が設定されていたため、問題が発生しませんでした:
// ローカル環境
process.env.NEXT_PUBLIC_SITE_URL = 'http://localhost:3000';
const origin = 'http://localhost:3000' || ('' + undefined) || '';
// ↓
const origin = 'http://localhost:3000'; // 最初の値が truthy なのでOK
Vercel環境では環境変数が未設定だったため、'undefined' という文字列が生成され、後続の処理でエラーが発生していました。
解決方法
修正前
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
修正後
const origin = process.env.NEXT_PUBLIC_SITE_URL || process.env.NEXT_PUBLIC_BASE_PATH || '';
不要な '' + を削除することで、意図した通りのフォールバック処理になりました。
より明示的な書き方
括弧を使って優先順位を明示することもできます:
// 括弧で優先順位を明示
const origin = (process.env.NEXT_PUBLIC_SITE_URL) || (process.env.NEXT_PUBLIC_BASE_PATH) || '';
// または、Nullish coalescing演算子を使用
const origin = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.NEXT_PUBLIC_BASE_PATH ?? '';
学んだこと
1. 演算子の優先順位を意識する
| 演算子 | 優先順位 |
|---|---|
() | 21(最高) |
+ (文字列連結) | 13 |
| ` | |
?? (Nullish coalescing) | 4 |
2. ローカルとサーバーの環境差異に注意
- ローカルで動いてもサーバーで動くとは限らない
- 環境変数の有無を常に意識する
- デフォルト値の設定を慎重に行う
3. 型の暗黙的変換に注意
'' + undefined // → 'undefined' (文字列)
'' + null // → 'null' (文字列)
'' + 0 // → '0' (文字列)
'' + false // → 'false' (文字列)
JavaScriptは型変換を自動で行うため、意図しない結果になることがあります。
まとめ
今回のバグは、一見シンプルなコードに潜む演算子優先順位の問題でした。
// この1行の違いが、ビルドの成否を分けた
A || '' + B || '' // バグ
A || B || '' // 正しい
JavaScriptの演算子優先順位は、普段あまり意識しないかもしれません。しかし、複数の演算子を組み合わせる際には注意が必要です。迷ったら括弧を使って明示的に記述することをお勧めします。