It Works on My Machine: What Changes When Your Code Goes Live?

It Works on My Machine: What Changes When Your Code Goes Live?

If you've been writing code for a while, there's a good chance you've experienced this at least once.

You build a feature, test it locally, and everything works fine. You refresh the page, submit the form a few times, check the layout on different screen sizes, and everything looks good.

So you deploy it.

Then things start going wrong.

Maybe an API request fails. Maybe a page that worked perfectly on your computer suddenly returns a 404. Or perhaps the application builds successfully but refuses to start on the server.

You go back to your code, looking for the problem, and everything seems fine.

I've always found this part of software development interesting because it shows that getting code to work is only one part of the job. Getting it to work reliably outside your development environment is a different challenge entirely.

So, what actually changes when you move an application from your computer to production?

Let's talk about some of the things that can go wrong and how to avoid making the same mistakes repeatedly.

1. Your Computer Is Not Your Production Server

One thing developers sometimes overlook is how much their local environment does for them.

You already have your dependencies installed. Your environment variables are configured. Your tools are available, and your application is running in an environment you're familiar with.

Your production server might be a completely different story.

For example, your local machine might be running Node.js 22, while your server uses an older version. Your application could depend on a package or feature that isn't supported in the production environment.

File paths are another common issue.

Consider this import:

import Header from './components/header.vue'

If the actual filename is Header.vue, the import might work on a case-insensitive filesystem but fail on a typical Linux production server, where filenames are case-sensitive.

It's a small detail, but it can stop a deployment in its tracks.

The same thing applies to dependencies. If you install packages without keeping track of their exact versions, a future installation could resolve to versions that behave differently from the ones you tested.

What I recommend: Keep your development and production environments as consistent as possible. Use a lockfile, agree on the Node.js version your project requires, and run a production build before deploying.

You won't always be able to make both environments identical, but you should at least know where they differ.

2. The Environment Variables You Forgot to Configure

This is another one worth paying attention to, especially when working with APIs.

Imagine you're building a Nuxt application that fetches data from a backend. Everything works locally because you've configured the API URL in your development environment.

You deploy the application, open the website, and the page loads. But the data never appears.

After checking the network requests, you discover that the application is sending requests to the wrong URL.

The problem isn't necessarily your API logic. Your production configuration might simply be missing or incorrect.

In Nuxt, you can define runtime configuration in nuxt.config.ts:

export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: '',
    public: {
      apiBase: '/api'
    }
  }
})

Here, apiSecret is intended for server-side use, while apiBase is available to client-side code.

That distinction matters. Anything exposed through public runtime configuration can be accessed by the browser, so private credentials should never go there.

Another thing to watch is the difference between build-time and runtime configuration. Some frameworks embed environment values during the build, while others let you provide certain values when the application starts.

If you're building an application once and deploying it to multiple environments, you need to know which configuration values can change without rebuilding it.

The takeaway: Don't assume that configuration on your computer automatically exists on your server. Document the variables your application needs, configure them in the correct environment, and validate important values before the application starts.

It's much easier to catch a missing variable early than to spend an hour debugging a feature that was never configured properly.

3. Your APIs and Third-Party Services Won't Always Cooperate

Most applications depend on something outside themselves.

It could be a payment gateway, an authentication service, an email provider, a database, or an API belonging to another company.

When everything is working, these integrations can feel straightforward. You send a request, receive a response, and move on.

But what happens when the service is unavailable?

Or when a request takes 30 seconds instead of two?

Or when the API returns an error you weren't expecting?

If your application assumes every request will succeed, even a temporary problem can create a bad experience for your users.

For example, a page might remain stuck on a loading indicator because nobody handled the failed request. A form might silently fail to submit, leaving the user unsure whether their information was saved.

These are the kinds of problems that are easy to miss when you only test the successful scenario.

A better approach is to plan for failure from the beginning.

Give requests reasonable timeouts where appropriate. Handle unsuccessful responses. Show users a useful message when something goes wrong, and provide a way to retry when that makes sense.

Be careful with retries, though. Retrying a request that retrieves data is different from retrying one that creates a payment or submits an order. If the first request succeeded but the response was lost, a second request could create a duplicate transaction.

For operations like these, idempotency keys and proper server-side checks can help prevent duplicate processing.

The point is simple: Your application doesn't control every service it depends on, so it shouldn't behave as though those services can never fail.

4. Stop Testing Only When Everything Goes Right

When I'm testing a feature, it's tempting to focus on the main thing I'm trying to achieve.

If I'm building a registration form, for example, I enter valid information and check whether the account gets created.

That test is necessary, but it doesn't tell me what happens when someone enters an invalid email address, leaves a required field empty, or loses their internet connection halfway through the process.

Those situations matter too.

A good application should handle more than the ideal scenario.

For a form, that means testing validation errors, failed requests, loading states, and repeated submissions. For an authenticated application, it also means checking what happens when someone tries to access a protected resource without permission.

Different types of tests can help here:

  • Unit tests check individual functions and components.

  • Integration tests check whether different parts of the application work together correctly.

  • End-to-end tests check important user journeys from beginning to end.

You don't need to write hundreds of tests before every release. Start with the parts of your application that matter most.

If you're building an e-commerce application, for example, payment processing, order creation, and authentication deserve particular attention.

A broken animation is inconvenient. A payment that gets processed twice is a much bigger problem.

Testing should reflect that difference.

5. Deployments Need a Process, Not Just a Button

Getting an application onto a server is often treated as the final step of development. In reality, deployment has its own set of things that can go wrong.

The server might use the wrong Node.js version. Environment variables might be missing. A reverse proxy could be pointing to the wrong port, or a database migration might not have been applied.

This is why I think every project should have a deployment process, even if it's a simple one.

For a typical web application, I would want to do the following before releasing a significant change:

  1. Commit the code to version control.

  2. Run the relevant tests and type checks.

  3. Create a production build.

  4. Test the application in a staging environment where possible.

  5. Verify the required environment variables and server configuration.

  6. Deploy the application.

  7. Check the logs and test the important features after deployment.

For larger projects, CI/CD pipelines can automate many of these steps. For smaller projects hosted through cPanel, the process might involve building the application, uploading the correct files, configuring the runtime, and restarting the application.

You don't need an elaborate deployment setup to be organised.

What matters is knowing exactly what needs to happen, doing it consistently, and having a plan for recovering when something goes wrong.

And please, don't assume that because the deployment command completed successfully, the application is working perfectly. Open the website and test it.

6. When Something Breaks, Can You Find Out Why?

Imagine a customer tells you that a feature isn't working.

You open the application on your computer, try the same steps, and everything works.

Now what?

Without useful logs or error reporting, you might spend hours trying to reproduce the problem.

This is where logging and monitoring become important.

Logging helps you understand what happened inside your application. Monitoring helps you keep track of how the application behaves over time.

Depending on the project, you might want to monitor server errors, API response times, failed requests, application availability, and other important metrics.

Error-tracking tools can also help you identify recurring exceptions and understand where they occurred.

You don't need a complicated monitoring setup for every website. Even basic server logs and an uptime monitor can make troubleshooting much easier.

One important thing: be careful about what you put in your logs. Passwords, access tokens, payment details, and other sensitive information should not be recorded unnecessarily.

For applications that communicate across multiple services, request IDs can also help you trace a single operation through the system.

The goal is to spend less time guessing and more time understanding the actual problem.

7. Think About the Developer Who Has to Maintain Your Code

There's one more issue that doesn't always show up immediately after deployment: code that becomes difficult to maintain.

You might duplicate the same logic across several components because it saves time. You might put API requests, validation, and presentation logic into one large component because it's convenient.

For a small feature, that might seem harmless.

But as the application grows, making changes becomes harder. Fixing something in one place might require updating several others, and adding a new feature can introduce unexpected bugs.

This is why code organisation matters.

In a Vue or Nuxt project, for example, you can break large components into smaller components with clear responsibilities. Repeated logic can sometimes be extracted into composables, while shared API operations can be organised in a way that makes them easier to maintain.

TypeScript can also help by making data structures and function contracts more explicit.

Of course, there's no need to overcomplicate a project just to make the architecture look impressive. Not every function needs an abstraction, and not every component needs to be split into five smaller components.

The aim is to make your code easier to understand and change as the project evolves.

A useful question to ask yourself is this: if I come back to this code six months from now, will I understand why I wrote it this way?

You don't have to predict every future requirement. Just give yourself, and anyone else working on the project, a reasonable starting point.

Before You Push That Update

Before deploying a significant change, take a few minutes to go through this checklist.

  • Does the production build complete successfully?

  • Are all required environment variables configured?

  • Have I checked that no private credentials are exposed?

  • Have I tested what happens when an API request fails?

  • Do the important features work outside my local environment?

  • Have I checked the server configuration and application logs?

  • Is there a way to recover if the deployment causes a serious problem?

  • Have I tested the application after deploying it?

Not every project needs the same release process, so adjust the checklist to fit the application you're working on. A small portfolio website and a payment-processing platform don't carry the same risks.

Still, taking a few minutes to check these things can save you a lot of unnecessary debugging later.

Final Thoughts

One thing worth remembering about software development is that the work doesn't end when the feature starts working on your computer.

The real test comes when other people start using it, when it runs in a different environment, and when things don't go exactly as expected.

You can't prevent every bug, network failure, or deployment issue. Even well-tested applications experience problems.

But you can make your applications more reliable by understanding your environment, handling errors properly, testing beyond the happy path, and paying attention to what happens after deployment.

You can also make life easier for yourself by keeping your code maintainable and putting a sensible release process in place.

So the next time you finish a feature and everything works perfectly, take a moment before calling it done.

Build it. Test it. Deploy it. Then check that it actually works where it matters.

Because there's a difference between code that works on your machine and software people can depend on.

And understanding that difference is an important part of becoming a better developer.

Share
Write by 266Labs

More from Jeremiah Chukundah, in one place

Save posts to your reading list and follow writers you like. Free for readers.

Save to your reading listFollow Jeremiah Chukundah
Back to the journal

Read next