DEV Community

Leo Brown
Leo Brown

Posted on

7 Things I Check When a React App Works Locally but Breaks in Production

7 Things I Check When a React App Works Locally but Breaks in Production

One of the most annoying frontend problems is this:

Your app works perfectly on localhost.

Then you deploy it.

And suddenly:

  • API requests fail
  • routes return 404
  • environment variables are missing
  • images disappear
  • authentication redirects break
  • everything works on your machine, but nowhere else

This happens often enough that I now go through roughly the same checklist every time.

Here are the things I usually check first.

1. Environment variables

Local development makes environment variables feel simple because .env files are always nearby.

Production is different.

A variable may:

  • not exist on the server
  • use a different name
  • contain an outdated URL
  • not be exposed to the browser
  • have been changed without rebuilding the app

For example, in Vite:

VITE_API_URL=https://api.example.com
Enter fullscreen mode Exit fullscreen mode

and:

const apiUrl = import.meta.env.VITE_API_URL;
Enter fullscreen mode Exit fullscreen mode

If you accidentally use:

API_URL=https://api.example.com
Enter fullscreen mode Exit fullscreen mode

the variable will not automatically be exposed to client-side code.

Next.js has a similar distinction:

NEXT_PUBLIC_API_URL=https://api.example.com
Enter fullscreen mode Exit fullscreen mode

is available in the browser, while:

API_URL=https://api.example.com
Enter fullscreen mode Exit fullscreen mode

is normally server-side only.

When something works locally but fails after deployment, I always inspect the actual production environment before changing application code.

2. Hardcoded localhost URLs

This one is extremely common.

During development, code slowly accumulates things like:

fetch("http://localhost:3001/api/users");
Enter fullscreen mode Exit fullscreen mode

or:

const socket = new WebSocket("ws://localhost:4000");
Enter fullscreen mode Exit fullscreen mode

Everything works locally because all services are running on the same machine.

In production, localhost means the user's own machine or the production server, depending on where the code executes.

Neither is usually what you want.

A better approach is to keep URLs in configuration:

const apiUrl = import.meta.env.VITE_API_URL;

fetch(`${apiUrl}/api/users`);
Enter fullscreen mode Exit fullscreen mode

I also search the whole project for:

localhost
127.0.0.1
http://
Enter fullscreen mode Exit fullscreen mode

before deploying.

It catches more issues than you might expect.

3. CORS

A request that succeeds locally can fail immediately when the frontend and backend are hosted on different origins.

Example:

Frontend:
https://app.example.com

API:
https://api.example.com
Enter fullscreen mode Exit fullscreen mode

The backend needs to allow requests from the frontend origin.

A typical Express configuration might look like:

import cors from "cors";

app.use(
  cors({
    origin: "https://app.example.com",
    credentials: true,
  })
);
Enter fullscreen mode Exit fullscreen mode

During debugging, I open the browser network panel and inspect the request directly.

If the browser says something like:

blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

then changing React code usually will not fix the problem.

The fix belongs on the API side.

4. Client-side routing

Single-page applications often have routes like:

/
/dashboard
/settings
/users/123
Enter fullscreen mode Exit fullscreen mode

Navigation inside the app works because React Router handles it.

But refreshing:

https://example.com/dashboard
Enter fullscreen mode Exit fullscreen mode

may return:

404 Not Found
Enter fullscreen mode Exit fullscreen mode

Why?

Because the web server tries to find an actual /dashboard file.

For a client-side SPA, the server usually needs to return index.html for unknown application routes.

For Nginx, that may look like:

location / {
    try_files $uri $uri/ /index.html;
}
Enter fullscreen mode Exit fullscreen mode

Platforms like Vercel, Netlify, Cloudflare Pages, and others have their own rewrite rules.

If navigation works but refreshing a route does not, routing configuration is one of the first things I check.

5. HTTP vs HTTPS

Browsers are much stricter once your site is served over HTTPS.

For example:

https://example.com
Enter fullscreen mode Exit fullscreen mode

calling:

http://api.example.com
Enter fullscreen mode Exit fullscreen mode

can be blocked as mixed content.

WebSockets have the same issue.

Instead of:

new WebSocket("ws://api.example.com");
Enter fullscreen mode Exit fullscreen mode

you may need:

new WebSocket("wss://api.example.com");
Enter fullscreen mode Exit fullscreen mode

A page can appear completely normal while some background functionality silently fails because the browser blocks insecure requests.

The console usually reveals this quickly.

6. File name case sensitivity

This bug can be surprisingly confusing.

On some local development systems, this may appear to work:

import Header from "./components/header";
Enter fullscreen mode Exit fullscreen mode

even if the real file is:

Header.jsx
Enter fullscreen mode Exit fullscreen mode

But a Linux production server may treat:

Header.jsx
Enter fullscreen mode Exit fullscreen mode

and:

header.jsx
Enter fullscreen mode Exit fullscreen mode

as different files.

The deployment build then fails, or the module cannot be resolved.

I try to keep import paths exactly aligned with filenames:

import Header from "./components/Header";
Enter fullscreen mode Exit fullscreen mode

This problem is especially easy to introduce when renaming files only by changing capitalization.

Git may not always detect the change the way you expect.

One workaround is:

git mv Header.jsx HeaderTemp.jsx
git mv HeaderTemp.jsx header.jsx
Enter fullscreen mode Exit fullscreen mode

7. Production build behavior

Development mode is not production mode.

React, Vite, Next.js, and bundlers can behave differently after optimization.

Before blaming the hosting provider, I build the application locally.

For Vite:

npm run build
npm run preview
Enter fullscreen mode Exit fullscreen mode

For Next.js:

npm run build
npm start
Enter fullscreen mode Exit fullscreen mode

This catches problems such as:

  • missing imports
  • invalid environment variables
  • SSR-only errors
  • browser-only APIs used on the server
  • build-time data fetching failures
  • TypeScript errors
  • incorrect asset paths

If the production build already fails locally, deployment is not the real problem.

My debugging order

When a deployment behaves differently from localhost, I usually check things in this order:

1. Browser console
2. Network requests
3. Production environment variables
4. API URLs
5. CORS
6. Routing/rewrite rules
7. HTTPS / mixed content
8. Production build logs
Enter fullscreen mode Exit fullscreen mode

This order saves me from randomly changing code.

The browser console and network panel alone often reveal the issue within a few minutes.

A small habit that helps

I try to avoid treating production configuration as something I think about only during deployment.

Instead, I keep local development reasonably close to production:

  • use environment variables from the start
  • avoid hardcoded URLs
  • test production builds locally
  • keep frontend and backend configuration separate
  • use HTTPS-compatible URLs
  • check routes with direct page loads

The closer the environments are, the fewer surprises appear later.

Final thought

When an app works locally but fails in production, the React code itself is often fine.

The problem is usually somewhere around it:

configuration
networking
routing
security
environment
build process
Enter fullscreen mode Exit fullscreen mode

Top comments (0)