"If it weren't for this CORS error, a ten-minute fix just cost me two hours." — After more than a decade of solo app development, I've lost count of how many times I've said that.
What makes CORS errors particularly frustrating is that the error message only tells you what was rejected, not why. When you ask Antigravity to "fix the CORS issue," it can't generate the right code unless you've pinpointed where the problem actually lives. I once had Antigravity add a cors middleware to an Express server while the real issue was my React frontend calling a Firebase REST endpoint directly — the fix did nothing.
This article maps out where CORS errors tend to originate in Antigravity-assisted development, and how to resolve them environment by environment.
Start by Identifying Which Boundary Is Failing
Before asking Antigravity to fix anything, you need to know which boundary is causing the error. CORS can break at any point where a browser request crosses an origin boundary, so narrowing it down first saves a lot of wasted effort.
Open DevTools, go to the Network tab, and look at the destination URL of the failing request. The pattern of the error message and destination will tell you which of these three scenarios you're in.
Pattern 1: Your frontend is calling an external API directly
The error reads something like: Access to fetch at 'https://api.stripe.com/...' from origin 'http://localhost:3000' has been blocked by CORS policy.
The provider's CORS policy — Stripe, Twilio, SendGrid, Google Maps — is blocking a direct browser-to-API call. This is the most common source of confusion for developers new to API integration.
Pattern 2: Your frontend and API server are running on different ports
The error reads: Access to fetch at 'http://localhost:8080/api/...' from origin 'http://localhost:3000' has been blocked by CORS policy.
Even though everything is "local," the browser treats different ports as different origins. This trips up developers who split frontend and backend into separate processes during development.
Pattern 3: A serverless function response is missing CORS headers
The error reads: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Cloudflare Workers, Firebase Functions, and Supabase Edge Functions don't add CORS headers by default. If your function doesn't explicitly include them, every browser request will fail.
Pattern 1 Fix: Route External API Calls Through Your Server
Calling external APIs directly from the browser exposes your API keys and triggers provider-side CORS restrictions. The solution is to proxy those calls through a server-side handler.
Here's a Next.js App Router Route Handler that wraps a SendGrid call:
// app/api/send-email/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body = await request.json();
// Server-to-server call — no CORS restrictions apply
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: [{ email: body.to }],
from: { email: "noreply@example.com" },
subject: body.subject,
content: [{ type: "text/plain", value: body.message }],
}),
});
if (!response.ok) {
return NextResponse.json({ error: "Send failed" }, { status: 500 });
}
return NextResponse.json({ success: true });
}Your frontend calls /api/send-email — a same-origin request — and never touches SendGrid directly.
When prompting Antigravity to implement this pattern, be explicit:
Currently, my frontend is calling the SendGrid API directly.
Refactor this so the call goes through a Next.js Route Handler.
Read the API key from the environment variable SENDGRID_API_KEY.
The endpoint should be /api/send-email, called from the frontend with fetch('/api/send-email', {...}).
Pattern 2 Fix: Use Next.js Rewrites to Proxy Locally
When your frontend and backend run on different ports during development, Next.js rewrites is the cleanest fix. It tells the Next.js dev server to forward certain requests to another origin — from the browser's perspective, it's all the same host.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async rewrites() {
if (process.env.NODE_ENV === "development") {
return [
{
source: "/api/backend/:path*",
destination: "http://localhost:8080/api/:path*",
},
];
}
return [];
},
};
export default nextConfig;Scoping this to development is intentional. You don't want this proxy logic leaking into production, where it could forward unexpected traffic to your backend.
Pattern 3 Fix: Add CORS Headers to Your Serverless Functions
Serverless runtimes don't add CORS headers automatically. You have to include them in every response — and critically, you have to handle the OPTIONS preflight request too.
For Cloudflare Workers:
// worker.ts
export default {
async fetch(request: Request): Promise<Response> {
const origin = request.headers.get("Origin") ?? "";
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
},
});
}
const data = { result: "success" };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": origin,
},
});
},
};In production, restrict which origins you allow rather than reflecting the request origin directly:
const ALLOWED_ORIGINS = [
"https://myapp.com",
"https://www.myapp.com",
];
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];For Supabase Edge Functions:
// supabase/functions/_shared/cors.ts
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};// supabase/functions/my-function/index.ts
import { corsHeaders } from "../_shared/cors.ts";
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
const data = { result: "success" };
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
});When prompting Antigravity for this kind of fix, naming the runtime avoids the generic cors package suggestion:
Add CORS headers to the Cloudflare Workers handler in worker.ts.
Allow only the origin https://myapp.com.
Support GET and POST methods, and handle OPTIONS preflight requests.
The Most Common Overlooked Issue: Preflight Requests
If you've added CORS headers and the error persists, the likely culprit is the OPTIONS preflight request not being handled.
Browsers send a preflight OPTIONS request before any request that includes Content-Type: application/json or an Authorization header. If your server returns a 404 or 400 for OPTIONS, the browser never sends the actual request — and you see a CORS error even though your headers look correct.
In DevTools, look at the request just before the failing one. If there's an OPTIONS request returning an error code, that's your problem. Tell Antigravity explicitly: "also handle the OPTIONS preflight request" and the generated code will include that branch.
Preventing Recurrence in Production
CORS issues that are solved locally often resurface after deployment. A few patterns to watch for:
Your dev environment used localhost as the origin, but production uses a different domain that isn't in your allowed list. Managing allowed origins through environment variables avoids this:
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS ?? "http://localhost:3000")
.split(",")
.map((o) => o.trim());Cloudflare's cache layer can also cause problems: if a CORS response is cached for one origin and served to a request from a different origin, the browser rejects it. Avoid caching responses that include Access-Control-Allow-Origin headers, or use a Vary: Origin header to tell the cache to key responses by origin.
Once you understand which of the three patterns you're dealing with, CORS stops being a mystery. The key to getting useful output from Antigravity is the same as always: be specific about your stack, your environment, and exactly what you need. Vague "fix CORS" prompts produce vague results; targeted prompts with context produce the right code on the first attempt.