ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-14Intermediate

CORS Errors Blocking API Tests in Antigravity — How to Diagnose and Fix by Environment

Fix CORS errors that block API testing in Antigravity development. Learn how to identify the root cause by environment — Next.js, Cloudflare Workers, Supabase — and craft precise prompts so Antigravity generates the right fix.

cors2api13troubleshooting108next.js4cloudflare6app-dev49

"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.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-06-19
Designing Safe Background Tasks with the Managed Agents API
Antigravity 2.0's Managed Agents API launches an agent in an isolated Linux environment with a single API call, handling reasoning, tool use, and code execution. Convenient, but left unattended it invites runaways and cost overruns. Here is a design for running it safely as a background task.
App Dev2026-05-17
When Antigravity Fixes a RecyclerView Crash but Breaks Something Else — The Defensive Copy Solution
If RecyclerView IndexOutOfBoundsExceptions keep coming back after Antigravity's fix, shared list reference is likely the cause. Here's how to prompt Antigravity for the right fix and encode the pattern in AGENTS.md.
App Dev2026-07-13
The Gate That Stops Visual Damage Before You Hand Bulk Image Optimization to an Agent
When an agent bulk re-encoded a few hundred wallpaper assets, a handful came back with dulled color. Size-reduction alone cannot catch that. Here is how to design a gate that stops bad conversions before merge using three axes — SSIM, ΔE, and file size — with a checker that runs on Pillow and scikit-image.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →