When something breaks
CORS error after deploy: frontend can't reach the backend
In short
A CORS error means the browser blocked the backend's response because the server did not allow requests from your frontend's address. The fix belongs on the backend: list the exact frontend origin, with https and no trailing slash, in its CORS settings. It worked locally because the Vite or Create React App dev server proxied your requests, so the browser saw the frontend and backend as one site. Also make sure the API URL in your frontend uses https and is set before the build, not after.
- CORS is enforced by the browser, so the same request from curl, Postman or a server goes through without an error.
- Two addresses are different origins if the scheme, domain or port differs: http://localhost:5173 and https://app.example.com are two separate origins.
- Browsers reject an Access-Control-Allow-Origin value of "*" when the request is sent with cookies or credentials.
- VITE_* and NEXT_PUBLIC_* variables are baked into the frontend code at build time, so after changing them the project has to be built and deployed again.
- A page loaded over https cannot call an API over http: the browser blocks it as mixed content.
Everything worked locally: the frontend loaded and data came back from the backend. After deploying, the page is empty, buttons do nothing, and the browser console shows a red line like "has been blocked by CORS policy: No Access-Control-Allow-Origin header is present". Yet if you open the API URL directly in the browser or call it with curl, the response is fine. This is not a hosting failure and not a bug in your logic, it is the browser protecting the user.
CORS, short for cross-origin resource sharing, is the rule that lets a page read a response from another site only if that site allows it. Once deployed, your frontend and backend live at different addresses, so to the browser they are two different sites, and the backend has to say explicitly that requests from your frontend are welcome. Below is why you did not need this locally and how to set it up properly.
| Framework | What to add | What to set |
|---|---|---|
| FastAPI | built-in CORSMiddleware | allow_origins with your frontend URLs; allow_credentials=True if you use cookies |
| Flask | the Flask-CORS package | CORS(app, origins=[frontend URL]); supports_credentials=True for cookies |
| Django | the django-cors-headers package | CORS_ALLOWED_ORIGINS with your URLs; CorsMiddleware placed as high as possible in MIDDLEWARE |
| Express | the cors package | app.use(cors({ origin: frontend URL, credentials: true })) before your routes |
| NestJS | built-in app.enableCors | origin set to your frontend URL and credentials: true for cookies |
| Spring Boot | @CrossOrigin annotation or addCorsMappings | allowedOrigins with your frontend URL; allowCredentials(true) for cookies |
Understand why there was no error locally#
During development Vite and Create React App usually proxy your requests: server.proxy in the Vite config or the proxy field in package.json for CRA forwards calls to /api to the backend. The browser only ever sees one address, localhost:5173, so CORS never kicks in. After the build the proxy is gone: what remains are static frontend files that call the backend directly at another address. That is why the error shows up only after deploying, even though the code has not changed.
Allow your frontend origin on the backend#
Turn on CORS support in the backend and list the exact address your frontend opens at: with https, with the domain, and without a trailing slash or path. In FastAPI that is the built-in CORSMiddleware, in Flask the Flask-CORS package, in Django the django-cors-headers package, in Express the cors package, and the exact settings are in the table. If you have both a local and a deployed frontend, list both. Keeping the list in an environment variable lets you change it without touching the code.
Do not use a wildcard if you send cookies#
A value of "*" means any site, which is fine for an open API without logins. But if the frontend sends cookies or uses credentials, the browser rejects a wildcard response and the CORS error stays, just with different wording. In that case set the specific frontend origin and enable credentials in your CORS settings. It is also safer: another site cannot make requests on behalf of your users.
Set the API URL before building the frontend#
The frontend learns the backend address from a variable: in Vite it must start with VITE_, in Next.js with NEXT_PUBLIC_, in Create React App with REACT_APP_. These are substituted into the code at build time, not when the page opens, so changing the value after the build does nothing. A reliable approach is a .env.production file in the frontend folder with a line like VITE_API_URL set to your backend address; it is not a secret, since any visitor can see it in the page code anyway. Deploy the frontend again after the change.
Make sure the API is called over https#
If the page is served over https but the API URL in the code starts with http, the browser blocks the request as mixed content, which is easy to mistake for the same CORS error. Change the URL to https. Also check that no http://localhost:8000 or similar local address is left in the code, because the deployed page would then try to reach the visitor's own computer instead of your server.
Make sure the backend answers the preflight request#
Before a request with JSON or an Authorization header, the browser first sends a short preflight request with the OPTIONS method and expects CORS headers in reply. A CORS library answers it for you, but if the backend protects every route with an auth check, that check may reject the preflight too, and the real request is never sent. The Network tab in the browser's developer tools shows which of the two requests failed. If OPTIONS failed, let it bypass the auth check.
A CORS error is almost always fixed by three things: the exact frontend origin in the backend’s CORS settings, an https API URL in the build-time variable, and a fresh deploy of the frontend. You can also avoid CORS entirely by serving the built frontend from the same app as the API, so both share one address. On Netrun every project gets its own HTTPS link, so a frontend and a backend deployed as separate projects are two origins and need CORS, and the backend’s live logs are right in your dashboard. Keep in mind that the free plan includes one project. Try Netrun.
Common questions
Why does the request work in Postman but not in the browser?
Because only the browser enforces CORS. Postman, curl and server code ignore the Access-Control-Allow-Origin header and show the response as is. The browser protects users from other sites and will not hand the response to a page script without the backend's permission.
Can I fix CORS on the frontend side?
No, only the server receiving the request can grant permission. The no-cors mode in fetch does not remove the error, it just hides the response body, so you still cannot read the data. Either configure CORS on the backend or serve the frontend and the API from one address.
What if the backend is not mine and I cannot configure it?
Then call it from your own server instead of the browser: your backend requests the third-party API and passes the result to the frontend. Server-to-server requests are not subject to CORS. As a bonus, your API keys stay on the server instead of ending up in the page code.
Is it safe to set allow_origins to a wildcard?
For an open API without logins or cookies it is acceptable: any site can read the responses, but they are public anyway. If you have logins, cookies or personal data, list your specific frontend origins. With a wildcard plus credentials the browser refuses anyway.
Why does the CORS error only happen sometimes?
A common reason is that the backend itself crashes with a 500 or 502, and such a response arrives without CORS headers, so the browser reports CORS even though the real problem is elsewhere. Check the backend logs at the moment of the error. If there is an exception, fix that, because your CORS settings are fine.
What about React, Vue, Next.js, Nuxt, Svelte or Astro?
Yes, all of them work. Upload the source code with your package.json — you do not need to build the project yourself or include the built output, the build happens on our side. If your framework can produce both static pages and a server build, either works: in server mode the app should listen on the port from the environment variable. One important detail: values baked into the built front-end are visible to any visitor, so keep real keys on the server side.
Where do I set tokens and other secret values?
Every project has a Secrets tab where you set the values from your code — for example the token from BotFather. We store them encrypted: you can see the variable names, but the values are shown to no one, including you.
Where can I see the logs and status of my project?
The project page shows the status, the logs and the event history — together they show what is happening with the project right now. The log view also reaches further back: scroll up and earlier lines load on their own.