はじめに

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の演算子優先順位は、普段あまり意識しないかもしれません。しかし、複数の演算子を組み合わせる際には注意が必要です。迷ったら括弧を使って明示的に記述することをお勧めします。

参考リンク