How to Fix CORS Error in React Localhost: Complete Guide
Understand exactly what the CORS error in React means, why Postman works but your browser does not, and all three ways to fix it: Express backend configuration, React proxy, Vite proxy, and production CORS setup.
It is 10:47pm. Your React app is talking to a Node.js backend and everything works perfectly when you test with Postman. You make the same request from React and you get a wall of red text in the browser console. You search Stack Overflow. The first answer tells you to add a header. The second contradicts it. The third works but breaks something else. An hour later you are more confused than when you started. This guide is what I wish I had that night.
The CORS Error in React Localhost
Here is the exact error that sends developers into a Stack Overflow spiral. You have seen this if you are reading this page:
Or this variant when you try to send authentication headers:
Both errors have a fix. But before you paste the fix into your code, understanding what actually caused this saves you a lot of grief later, especially when you hit the second or third variant.
What Is CORS and Why Does the Browser Block the Request?
CORS stands for Cross-Origin Resource Sharing. It is a security feature built into every web browser that restricts JavaScript from making requests to a different origin than the page it is running on. It exists to protect you as a user. Without CORS, any malicious website could make requests to your bank on your behalf, using your browser’s stored cookies and authentication.
The security model works like this: when your JavaScript code in a browser makes a fetch request to a different origin, the browser checks whether the server at that origin has explicitly permitted requests from your page’s origin. It does this by looking for specific HTTP response headers. If those headers are missing or do not include your origin, the browser blocks the response from reaching your JavaScript code. The request still went to the server. The server still responded. The browser intercepted the response and threw it away.
CORS is enforced by the browser, not the server. Your backend is working perfectly. Your request is reaching the server. The browser is the one blocking the response. That is why Postman works and your React app does not.
Why Postman Works But React Gets Blocked
This is the most confusing part for developers encountering CORS for the first time. You test your API in Postman. It works. You hit the same endpoint from React. CORS error. Is the backend broken? No. Is your fetch code wrong? No. So what is happening?
Postman is not a browser. It does not have a security origin. It does not enforce CORS. When Postman sends a request, it goes directly to your server, gets the response, and shows it to you with no filtering. When your React app sends the same request, the browser checks the response headers, finds no permission to share the response with your React code’s origin, and blocks it.
Your backend is healthy. Your request code is correct. The CORS error is the browser doing its job. The fix is telling the browser that your backend intends to allow requests from your React app’s origin, which you do by adding the right headers to your backend’s responses.
What Counts as a Different Origin?
An origin is the combination of three things: protocol, domain, and port. Two URLs are the same origin only if all three parts match exactly. If any one differs, they are different origins and CORS applies.
Same computer. Same localhost hostname. But different ports. The browser treats them as completely separate origins and the CORS check applies. This surprises almost everyone the first time. You might reasonably assume localhost is localhost. The browser disagrees.
The same rule applies in production. Your React app at https://app.yourdomain.com and your API at https://api.yourdomain.com are different origins even though they share the same root domain. Subdomain differences count as different origins under the browser’s security model.
This is the correct fix in almost every situation. The backend sends response headers that tell the browser which origins are allowed to read the response. Once the browser sees your React app’s origin in those headers, it stops blocking. This fix works in development and in production, and it is the only fix that scales correctly.
Here is how to add CORS support for the three most common backend frameworks:
In Express, this catches many developers: the app.use(cors()) call must come before any route definitions. If you place it after your routes, the routes handle the request before CORS headers are added and the browser gets a response with no CORS headers. In Django, CorsMiddleware must be the first item in the MIDDLEWARE list for the same reason.
If you cannot or do not want to modify the backend, the React development server can act as a proxy. When your React code calls /api/users, the dev server forwards that request to your backend at http://localhost:5000/api/users. Because the request comes from the dev server rather than the browser, CORS does not apply. The browser sees the response as coming from the same origin as the React app, so it allows it through.
This proxy works in development only. It has no effect on a production build. For production, you need the backend CORS headers from Method 1, or you need to serve both the frontend and backend from the same origin.
Add one line to your package.json:
Then change your fetch calls to use relative paths instead of the full URL:
Restart your React dev server after adding the proxy. The dev server does not pick up package.json changes automatically.
Vite uses a configuration file instead of package.json for proxy settings:
One thing Vite handles better than Create React App is proxying to multiple different backend services. If your React app talks to a main API on port 5000, an authentication service on port 5001, and a file service on port 5002, you can proxy each path prefix to a different backend target. This is handy for microservices setups during development.
Sometimes you are calling a third-party API that simply does not send CORS headers. Maybe it is an older API designed for server-to-server use, or the API provider just never added browser support. In development, you can route the request through a CORS proxy service that adds the necessary headers.
Public CORS proxy services like cors-anywhere are for development and quick testing only. In production, your requests would pass through a third party’s server: they can see your API keys, your request data, and your users’ data. They can also go down at any time, taking your app with them. The production solution is to route the request through your own backend, which makes the request server-to-server where CORS does not apply. Your backend acts as the middleman and forwards the response to your frontend.
The CORS Error Variants Decoded
The CORS error comes in several flavours, each pointing to a different specific problem. Reading the exact error message saves a lot of time:
The Part Nobody Covers: CORS in Production
Fixing the CORS error in React localhost development is one problem. Deploying to production and discovering the CORS error is back is another problem entirely, and it catches people who only half-understood the development fix.
In production, your React app is typically hosted at something like https://app.yourdomain.com. Your API runs at https://api.yourdomain.com. Different subdomains, different origins. The browser still enforces CORS. If your backend still has origin: ‘http://localhost:3000’ hardcoded, every request from the production frontend is blocked.
The correct approach is to use environment variables for the allowed origin so you can set the right value for each environment without changing code:
The wildcard origin (origin: ‘*’) combined with credentials: true is both a security vulnerability and a specification violation that browsers refuse to honour. With a wildcard allowed origin, any website could make authenticated requests to your API using your users’ cookies. With specific origins, only your own frontend can make authenticated requests. Always use specific allowed origins in production environments that handle authentication.
7-Step Debugging Guide When Nothing Seems to Fix the CORS Error
You have added the cors middleware. You restarted the server. The error is still there. Here is the methodical approach that always finds the problem:
- Confirm the backend is actually running and receiving requests. Open your browser and navigate directly to the backend URL: http://localhost:5000/api/users. If you get a response (even a JSON error), the backend is running. If you get a “connection refused” error, the backend is not running or is on a different port. Fix the running issue first before debugging CORS.
- Check the Response Headers in the Network tab of browser DevTools. Open DevTools, go to the Network tab, make the request from your React app, click the failed request in the list, and go to the Response Headers section. Do you see an Access-Control-Allow-Origin header? If there is no such header at all, the cors middleware is not running. If the header is present but has the wrong value, the allowed origin does not match your React app’s origin exactly.
- Check that the cors middleware is placed before all route definitions in Express. In your server file, look at the order of your app.use() calls. The cors() middleware must appear before any app.get(), app.post(), or app.use(‘/api’, router) lines. If routes are defined before cors is applied, those routes handle requests before CORS headers are added.
- Verify the allowed origin matches exactly what the browser sends. The Origin header the browser sends must exactly match what you configured in cors({ origin: ‘…’ }). Check the Network tab Request Headers for the Origin header. Copy that value exactly (no trailing slash, correct protocol, correct port) into your cors config. A mismatch of even one character prevents the browser from accepting the response.
- For the credentials wildcard error, change origin from ‘*’ to the specific origin. If you are getting the “wildcard not allowed with credentials” error, change origin: ‘*’ to origin: ‘http://localhost:3000’. Add credentials: true to your cors config. Confirm your fetch call includes credentials: ‘include’. All three must be consistent for authenticated cross-origin requests to work.
- Inspect the actual API response body by checking the JSON Formatter. Once CORS is fixed and you are getting responses through, paste the API response body into the JSON Formatter to see the exact structure. This is particularly useful when debugging API responses in development because the Network tab Response panel can be hard to read for deeply nested JSON objects, and understanding the structure is essential before writing React code that depends on it.
- For production deployments, check the ALLOWED_ORIGINS environment variable in your hosting platform. If CORS worked in development but broke in production, the most common cause is the allowed origin is still set to localhost:3000 rather than your production frontend URL. Log in to your hosting platform (Heroku, Railway, Render, AWS, etc.), find the environment variables section, and confirm ALLOWED_ORIGINS is set to your production frontend URL. Redeploy after the change if the platform requires it.
Frequently Asked Questions About CORS Errors in React
There are three common reasons. First, the cors middleware is placed after your route definitions. Express middleware executes in the order it is defined: if a route handler runs before cors runs, the response is sent without CORS headers. Move app.use(cors()) to the top of your middleware chain, before any route definitions. Second, you may have restarted the frontend dev server but not the backend. The backend needs to restart for the new cors middleware to take effect. Third, confirm the cors package is actually installed. Run npm list cors and if it is not listed, run npm install cors and restart the server.
Simple GET requests and some POST requests with specific content types are called “simple requests” and do not trigger a preflight. However, a POST request with Content-Type: application/json is not a “simple request” and triggers a preflight OPTIONS request first. If your cors middleware is not handling OPTIONS requests, the preflight fails and the browser blocks the actual POST. Ensure your cors middleware is applied globally (not just to specific routes) and includes ‘OPTIONS’ in the allowed methods list. Using app.use(cors(config)) rather than route-specific cors handles this automatically.
For long-term projects where you control the backend, configuring the backend to send correct CORS headers is the better solution. It works in both development and production, it is how CORS is intended to work, and it does not depend on the dev server setup. The proxy approach only works in development (the proxy field in package.json has no effect on production builds) and requires changing all your fetch URLs to relative paths. If you are calling a backend you do not control, or want a quick development workaround while you wait for backend changes, the proxy is fine. Just remember to add proper CORS headers to the backend before deploying to production.
Almost certainly your allowed origin is still set to http://localhost:3000 in the production backend. The origin the browser sends in production is your production frontend URL (like https://app.yourdomain.com), which does not match localhost:3000, so the browser blocks the response. Check your production environment variables and update the allowed origin to your production frontend URL. If you are also using the React dev server proxy, remember that it only works in development: your production React app fetches directly from the backend URL, not through a proxy.
It depends entirely on whether your API uses authentication. If your API is fully public (no login, no user-specific data, no sensitive operations), allowing all origins with cors() is technically fine. Any website can already call your public API by other means. If your API uses cookies, session tokens, or Authorization headers, allowing all origins creates a security risk: a malicious website could trick a logged-in user’s browser into making authenticated requests to your API. In that case, always specify exact allowed origins and use credentials: true only for those specific trusted origins. When in doubt, be specific rather than permissive.
This is one of the most common CORS misunderstandings. The Access-Control-Allow-Origin header is a response header: it must be sent by the server in its response, not by the browser in its request. Adding it to your frontend fetch call does nothing useful because the browser ignores custom Access-Control-* headers in requests. The fix is always on the server side: the server needs to include Access-Control-Allow-Origin: http://localhost:3000 in its responses. The browser reads this header from the response and decides whether to allow your JavaScript code to access the response body.
Tools for debugging API responses once CORS is fixed
Inspect and format JSON API responses, compare responses between environments, convert data formats, and more. All free, all in your browser, no login required.
CORS Is Not Your Enemy. It Is Just a Browser Enforcing a Contract Your Backend Has Not Signed Yet.
The moment this clicked for me was when I stopped thinking of CORS as a bug and started thinking of it as the browser asking the server for written permission. When you add app.use(cors({ origin: ‘http://localhost:3000’ })), you are telling the browser: my server explicitly permits requests from that origin. The browser sees that permission in the response headers and steps aside. Without that permission, it protects the user by blocking the response, exactly as designed.
The three takeaways that prevent the CORS confusion loop: CORS is enforced by the browser, not the server (which is why Postman works). The fix always lives on the backend, not in your fetch code. And in production, your allowed origin must be your production frontend URL, not localhost. Get those three things right and you will rarely fight CORS again.
Once your CORS error is resolved and API responses are flowing through, paste the response body into the JSON Formatter to understand the exact data structure before writing the React code that consumes it. Understanding the shape of the data first saves a lot of time debugging undefined property errors later.