Introduction
When trying to deploy a Next.js application to Vercel, we encountered a problem where the build succeeded locally but failed on Vercel. The error messages were vague, making it difficult to identify the cause.
This article shares the process from discovering the problem to resolving it, and summarizes what we learned about JavaScript operator precedence.
Symptoms
Error Message
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.]
Notable Behavior
- Local build succeeds, but Vercel build fails
- Errors occur on different pages each time (22-03, 24-03, 25-04, etc.)
- Error details are hidden in production builds
Discovering the Cause
While investigating multiple files, we found the following code pattern:
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
It looks harmless at first glance, but an operator precedence trap was lurking here.
What Is Operator Precedence?
In JavaScript, operators have precedence. It works the same way as in mathematics, where “multiplication is calculated before addition.”
Precedence Examples
// Same as mathematics
2 + 3 * 4 // -> 2 + 12 -> 14 (3 * 4 is evaluated first)
// In the case of string concatenation (+) and logical OR (||)
// + precedence: 13
// || precedence: 5
// -> + is evaluated first!
Breaking Down the Problematic Code
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
This code is interpreted as:
const origin = process.env.NEXT_PUBLIC_SITE_URL || ('' + process.env.NEXT_PUBLIC_BASE_PATH) || '';
// ^ This is evaluated first
When Environment Variables Are Undefined
// NEXT_PUBLIC_SITE_URL = undefined
// NEXT_PUBLIC_BASE_PATH = undefined
const origin = undefined || ('' + undefined) || '';
// |
const origin = undefined || 'undefined' || '';
// |
const origin = 'undefined'; // Becomes the string 'undefined'!
'' + undefined becomes the string 'undefined'. This is a result of JavaScript’s type coercion specification.
Why It Succeeded Locally
In the local environment, the environment variables were set, so the problem did not occur:
// Local environment
process.env.NEXT_PUBLIC_SITE_URL = 'http://localhost:3000';
const origin = 'http://localhost:3000' || ('' + undefined) || '';
// |
const origin = 'http://localhost:3000'; // First value is truthy, so OK
In the Vercel environment, the environment variables were not set, so the string 'undefined' was generated, causing errors in subsequent processing.
Solution
Before Fix
const origin = process.env.NEXT_PUBLIC_SITE_URL || '' + process.env.NEXT_PUBLIC_BASE_PATH || '';
After Fix
const origin = process.env.NEXT_PUBLIC_SITE_URL || process.env.NEXT_PUBLIC_BASE_PATH || '';
By removing the unnecessary '' +, the fallback processing now works as intended.
More Explicit Syntax
You can also use parentheses to make the precedence explicit:
// Make precedence explicit with parentheses
const origin = (process.env.NEXT_PUBLIC_SITE_URL) || (process.env.NEXT_PUBLIC_BASE_PATH) || '';
// Or use the nullish coalescing operator
const origin = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.NEXT_PUBLIC_BASE_PATH ?? '';
Lessons Learned
1. Be Aware of Operator Precedence
| Operator | Precedence |
|---|---|
() | 21 (highest) |
+ (string concatenation) | 13 |
| ` | |
?? (nullish coalescing) | 4 |
2. Watch Out for Environment Differences Between Local and Server
- Just because it works locally does not mean it will work on the server
- Always be aware of whether environment variables are set
- Set default values carefully
3. Watch Out for Implicit Type Coercion
'' + undefined // -> 'undefined' (string)
'' + null // -> 'null' (string)
'' + 0 // -> '0' (string)
'' + false // -> 'false' (string)
JavaScript performs type conversion automatically, which can lead to unintended results.
Summary
This bug was an operator precedence issue hidden in seemingly simple code.
// This single-line difference determined whether the build succeeded or failed
A || '' + B || '' // Bug
A || B || '' // Correct
You may not usually think much about JavaScript operator precedence. However, caution is needed when combining multiple operators. When in doubt, we recommend using parentheses to write code explicitly.