Blog #1: When 'It works on my machine' is the sweetest lie
The story of a fateful Friday afternoon, when the production build decided to 'betray' me over a few code optimization techniques I never paid attention to.
It was a Friday afternoon, 4:30 PM. The whole team was excitedly preparing for the launch of the new Checkout feature for our E-commerce system. After two weeks of intense coding, everything on my local machine was running flawlessly. I confidently hit merge, CI/CD hummed along, and the pipeline turned green. I smiled, closed my laptop, and told myself: "This is going to be a peaceful weekend."
But just 15 minutes later, my Slack exploded. "Uh, the Pay button isn't working!", "Logs are reporting e is not a function, u is undefined...". I opened the production link, and sure enough, the Payment button sat there like a rock. Opening the console, I saw a jumble of characters that no human could decipher.
At that moment, the confidence I had earlier completely vanished, replaced by a cold chill creeping down my neck. I had just realized I'd made the most elementary mistake: trusting only the Development environment.
1. Why did the source code "betray" us?
When we run in a Development environment, the code is kept in its original state for easy debugging. But when you run the build command, Webpack or Vite calls upon aggressive "blacksmiths" like Terser or UglifyJS.
Their mission is Minification and Mangling. They will:
- Rename long variable names like
customerProfiletoa. - Strip away all comments and whitespace.
- Even delete pieces of code it "thinks" are unused (Tree Shaking).
And sometimes, what these build tools "think" is vastly different from the logic we defined.
2. The Problem: When logic relies on "identifiers"
Our system at the time used a custom Validation library. For flexibility, I had written a piece of logic to automatically get the name of Classes or Functions to map them to rules in the database.
// The "clever" code that cost me dearly
function validate(fieldObject) {
const constructorName = fieldObject.constructor.name;
// In Dev: constructorName = "UserEmail"
// In Prod: constructorName = "t" (Boom!)
return rules[constructorName].check(fieldObject.value);
}
In the Dev environment, constructor.name correctly returned "UserEmail". But on Production, the Minifier had aggressively renamed the UserEmail class to a single character: t. Naturally, rules['t'] was undefined. As a result, the entire payment logic was paralyzed.
3. The Process: Panic and Wrong Assumptions
Initially, I frantically checked the API Keys. I thought maybe the .env.production file hadn't been loaded correctly, causing the payment API to return an error. I spent 30 minutes re-checking every variable on the Cloud.
My next mistake was trying to debug directly on the production site using Source Maps... but we had disabled Source Maps on Prod for source code security. I was lost in a maze of variables named a, b, c, t, u.
The lifesaver was running npm run build locally and using the serve plugin to run that build. For the first time, I actually saw the error appear on my own machine. It was a mix of relief for finding the cause and self-reproach for not doing this sooner.
The final fix: I had to give up on relying on constructor.name. Instead, I added a static identifier attribute (static id = 'UserEmail') to each class. A more manual solution, but "immune" to any minifier.
4. Counter-argument: Was I over-engineering?
Looking back at that automatic mapping logic, I wonder if I was just trying to "act smart." Writing code to automatically get class names looks professional and keeps the code concise, but it creates a hidden dependency on build tools that you don't control 100%.
Sometimes, writing a few more lines of manual but explicit code is infinitely better than "magic" but fragile logic.
5. Junior vs Senior: Approaches to Environment Differences
A Junior developer often only cares if the feature is finished. When they hit a build error, they tend to look for configurations to make the Minifier "spare" that piece of code (e.g., keep_fnames: true). This is a quick fix, but it increases bundle size and doesn't solve the root problem.
A Senior developer understands that the Production environment is immutable and harsh. Instead of forcing the environment to adapt to their code, they write code so it can run anywhere, no matter how much it's renamed or minified.
Sustainable solutions:
- Build a strict staging process where the source code is built 1:1 with production.
- Always run periodic build tests locally before merging complex logic.
- Stay away from techniques that rely on unstable metadata like function names or class names.
6. Lessons Learned
After that incident, I understood one thing more clearly: The distance from the developer's machine to the user's browser isn't just a few git clicks away. It's a process of physical and chemical transformation of the source code.
My advice to you: Don't wait until production crashes to start running the build command. Make it a habit to check the final build at least once before handing it over. And most importantly, always keep your code "clean" and explicit—don't let "magic" blind you.
We were lucky to fix the error in 2 hours and re-release before the deadline. But the price was the team's trust and a sleepless Friday night. Don't be like me back then.
Source code may change, but heart-earned caution does not.
Series • Part 1 of 50