Article
CORS Errors and API Integration Nightmares in Vibe-Coded Apps
Access blocked by CORS policy, API calls timing out, and mysterious 403 errors—why API integrations fail in production and how to fix them for good.
CORS Errors and API Integration Nightmares in Vibe-Coded Apps
Your app loads perfectly. Users can navigate around, see content, interact with the UI. Then they try to use a feature that calls an external API—and everything breaks. The browser console shows the dreaded message: "Access to fetch at 'api.example.com' has been blocked by CORS policy."
CORS errors and API integration failures are among the most confusing issues for non-technical founders, yet they're incredibly common in AI-generated applications. Let's demystify them.
What Is CORS and Why Does It Exist?
Cross-Origin Resource Sharing (CORS) is a security feature built into web browsers. It prevents malicious websites from making unauthorized requests to APIs on your behalf.
How It Works
When your app at https://yourapp.com tries to call an API at https://api.stripe.com, the browser:
- Sends a preflight request (OPTIONS) to the API
- Checks if the API allows requests from
yourapp.com - If allowed, sends the actual request
- If not allowed, blocks it and shows a CORS error
The Development vs. Production Gap
AI coding tools often run in special environments that bypass CORS during development:
- Bolt.new WebContainer - No CORS restrictions in preview
- Replit - Proxy handles CORS automatically
- v0 Preview - Runs on localhost, often CORS-free
- Lovable Preview - Built-in CORS bypass
Then you deploy to production, and suddenly every API call fails.
The Four Types of CORS Errors
1. No 'Access-Control-Allow-Origin' Header
Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
Translation: The API server didn't explicitly allow your domain to access it.
Fix (if you control the API):
// Express.js example
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://yourapp.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
Fix (if you don't control the API): Use a backend proxy (explained below).
2. Credentials Mode Mismatch
Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Credentials' header in the response is ''
which must be 'true' when the request's credentials mode is 'include'.
Translation: You're trying to send cookies/auth headers, but the API isn't configured for it.
Fix:
// Frontend
fetch('https://api.example.com', {
credentials: 'include', // Sends cookies
});
// Backend must respond with
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Origin', 'https://yourapp.com'); // Can't use *
3. Preflight Request Failure
Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not
have HTTP ok status.
Translation: The OPTIONS request (preflight) failed before the actual request was sent.
Fix: Handle OPTIONS requests explicitly:
// Express.js
app.options('*', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://yourapp.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(200);
});
4. Wildcard with Credentials
Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Origin' header in the response must not
be the wildcard '*' when the request's credentials mode is 'include'.
Translation: You can't use Access-Control-Allow-Origin: * when sending cookies.
Fix:
// ❌ Won't work with credentials
res.header('Access-Control-Allow-Origin', '*');
// ✅ Specify exact origin
const allowedOrigins = ['https://yourapp.com', 'https://staging.yourapp.com'];
if (allowedOrigins.includes(req.headers.origin)) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
}
The Backend Proxy Solution
When you don't control the API (using third-party services like weather APIs, stock data, etc.), create a proxy through your own backend:
Why This Works
- Your frontend calls YOUR backend (same origin, no CORS)
- Your backend calls the third-party API (server-to-server, no CORS)
- Your backend returns the data to your frontend
Implementation
// Backend proxy endpoint (Next.js API route example)
// pages/api/proxy.ts
export default async function handler(req, res) {
const { url, ...options } = req.body;
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`,
},
});
const data = await response.json();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: 'API call failed' });
}
}
// Frontend call
const data = await fetch('/api/proxy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://external-api.com/data',
method: 'GET',
}),
});
Bolt.new WebContainer CORS Limitations
Bolt.new runs in a WebContainer, which has built-in CORS restrictions that cannot be disabled through the UI. As of December 2024, StackBlitz is working on improving CORS handling, but limitations remain.
Workarounds for Bolt.new
1. Use CORS-Anywhere Proxies (development only)
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
const targetUrl = 'https://api.example.com/data';
fetch(proxyUrl + targetUrl);
⚠️ Warning: Never use public proxies in production—they can steal your data.
2. Configure API Provider
Some APIs let you whitelist the Bolt.new preview domain:
- Find your Bolt.new preview URL (e.g.,
abc123.bolt.new) - Add it to your API's allowed origins
3. Move to Production
WebContainer CORS issues often disappear when deploying to a real hosting platform like Vercel or Netlify.
Edge Function CORS Errors
Cloudflare Workers, Vercel Edge Functions, and Netlify Edge Functions have unique CORS requirements.
Common Edge Function CORS Mistake
// ❌ Missing CORS headers in edge function
export default async function handler(req: Request) {
const data = await fetchSomeData();
return new Response(JSON.stringify(data));
}
When called from a different origin, this fails.
Correct Edge Function CORS
// ✅ Proper CORS headers
export default async function handler(req: Request) {
// Handle preflight
if (req.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
const data = await fetchSomeData();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
API Key Exposure in Client-Side Code
AI tools frequently commit this cardinal sin: putting API keys directly in frontend code.
// ❌ API key visible to anyone
const response = await fetch('https://api.openai.com/v1/chat', {
headers: {
'Authorization': 'Bearer sk-abc123...', // Exposed!
},
});
Anyone can open DevTools, see your API key, and rack up charges on your account.
The Secure Pattern
// ✅ Backend handles API calls
// Frontend
const response = await fetch('/api/openai', {
method: 'POST',
body: JSON.stringify({ prompt: 'Hello' }),
});
// Backend (keeps API key secret)
export default async function handler(req) {
const { prompt } = req.body;
const response = await fetch('https://api.openai.com/v1/chat', {
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({ prompt }),
});
return response.json();
}
API Rate Limiting and Timeouts
AI-generated code rarely includes retry logic, rate limit handling, or proper timeout configuration.
Problems This Causes
- API calls fail randomly when you hit rate limits
- Slow APIs cause the entire app to hang
- No user feedback when requests take too long
Robust API Call Pattern
async function fetchWithRetry(url: string, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeout);
// Handle rate limiting
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 5;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, 2 ** i * 1000));
}
}
}
Webhook Integration Failures
AI tools set up webhooks, but they fail in production due to:
1. Wrong Webhook URL
// ❌ Still pointing to localhost
const webhookUrl = 'http://localhost:3000/api/webhook';
// ✅ Use production URL
const webhookUrl = process.env.WEBHOOK_URL || 'https://yourapp.com/api/webhook';
2. Missing Signature Verification
Webhooks without verification let attackers send fake events:
// ✅ Verify webhook signatures (Stripe example)
import Stripe from 'stripe';
export default async function handler(req) {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
const event = Stripe.webhooks.constructEvent(
req.body,
sig,
webhookSecret
);
// Process event
} catch (err) {
return new Response('Webhook signature verification failed', { status: 400 });
}
}
3. Blocking Webhook Responses
Webhooks must respond quickly (< 5 seconds). Process heavy tasks asynchronously:
// ✅ Respond immediately, process later
export default async function handler(req) {
const event = await validateWebhook(req);
// Acknowledge receipt immediately
res.status(200).send('Received');
// Process asynchronously (job queue, background worker)
await queue.add('process-webhook', { event });
}
When API Issues Get Complex
Sometimes the problem isn't obvious:
- API works in Postman but fails from your app
- Intermittent failures only under load
- API calls succeed but return wrong data
- Requests work on desktop but fail on mobile
These scenarios often involve headers, authentication flows, or platform-specific networking quirks that require expert debugging.
API integrations blocking your launch? Book a hardening sprint and we'll integrate, secure, and optimize your APIs professionally.