Jamstack Static Site API Calls Work Locally but Fail CORS in Production
0 reputation · 17 Sept 2026, 21:29 UTC
0 reputation · 17 Sept 2026, 21:29 UTC
A Jamstack static site fetches a backend API from the browser. During local development, a dev-server proxy routes /api to localhost, so the browser treats the calls as same-origin and CORS is not exercised. In production, the site is served from a CDN or custom domain and fetches a different API origin directly, making the requests cross-origin.
The goal is to call the API from the production origin without CORS failures while keeping the target changeable without redeploying. Build-time environment variables are embedded in the deployed JavaScript, so changing the target requires a new build and exposes embedded keys. Runtime configuration or edge rewriting could avoid that, but the API must allow the exact production scheme, host, and port in Access-Control-Allow-Origin. Wildcard origins are not honored with credentials.
It is unresolved whether the API base URL should be resolved at build time or runtime, and which production origins should be permitted before deployment.
Should the API base URL be injected at build time or resolved at runtime in the browser?
Which exact production origins must the API allow in Access-Control-Allow-Origin?
Does the API need to handle OPTIONS preflight requests for the production origin, methods, and headers?
35275 reputation · 18 Sept 2026, 06:35 UTC
To resolve CORS failures in production while maintaining flexibility, you should resolve the API base URL at runtime and configure your backend to explicitly allow your production origin (including scheme and port) and handle OPTIONS preflight requests.
Injecting the API URL at build time creates a static artifact that requires a full redeploy to change targets. To avoid this and prevent embedding environment-specific keys in the build, use a runtime resolution strategy:
/api/* requests to the backend origin. This makes the request appear same-origin to the browser, bypassing CORS entirely.config.json file from the root of your production site at startup to determine the API endpoint.If you choose to call the API directly from the browser (cross-origin), the backend must meet these three criteria:
Access-Control-Allow-Origin header must match the production origin exactly (e.g., https://app.example.com). A wildcard (*) will cause the browser to block the request if you are sending credentials (cookies or Authorization headers).OPTIONS requests with a 200 OK or 204 No Content status. This "preflight" check occurs before the actual request to verify permitted methods and headers.Access-Control-Allow-Methods (e.g., GET, POST, PUT, DELETE) and Access-Control-Allow-Headers (e.g., Content-Type, Authorization).Verify the fix using the browser's Network tab or a scoped cURL command to simulate the production origin:
curl -v -X OPTIONS https://api.yourbackend.com/endpoint \
-H "Origin: https://your-production-site.com" \
-H "Access-Control-Request-Method: POST"
Ensure the response contains Access-Control-Allow-Origin: https://your-production-site.com.
Are you using withCredentials: true (or sending cookies/HTTP Auth) in your API requests? If so, the backend must also return Access-Control-Allow-Credentials: true, and the origin cannot be a wildcard.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 18 Sept 2026, 03:37 UTC
To avoid the trade-off between build-time embedding and complex CORS configurations, consider using Edge Functions or CDN Rewrites (such as Netlify Redirects or Vercel Rewrites). This approach mirrors the local dev-server proxy by routing /api/* requests to the backend origin at the network edge.
Access-Control-Allow-Origin headers and OPTIONS preflights.withCredentials: true works natively without requiring specific origin matching.To verify this setup, check the browser Network tab: the request URL should remain /api/... rather than showing the full backend domain.