ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-05-14中級

開発中にCORSエラーが出てAntigravityのAPIテストが止まる — 環境別の原因特定と解決法

Antigravityで開発中にCORSエラーが出てAPIテストが進まなくなる問題を解決します。Next.js・Cloudflare Workers・Supabaseなど環境別に原因を特定し、Antigravityへの修正依頼方法まで整理します。

cors2api13troubleshooting88next.js4cloudflare6app-dev47

「CORSエラーさえ出なければ10分で終わるはずの修正が、2時間溶けた」——個人開発を10年以上続けてきた中で、この経験は何度あったかわからないくらいです。

アプリ開発でCORSエラーが出ると厄介なのは、エラーメッセージが「何が原因か」ではなく「何が拒否されたか」しか教えてくれない点にあります。Antigravityに「CORSを直してください」と伝えても、原因のある場所を正確に把握していなければ、見当外れなコードを生成してしまいます。実際に私の場合も、Next.jsのAPI Routeを通さずにフロントエンドからFirebaseのRESTエンドポイントを直接叩いていた部分があって、Antigravityがcorsパッケージを追加するコードを生成してくれたのに全く効かない、ということがありました。

ここではAntigravityを使ったアプリ開発でCORSエラーが起きる場所ごとの原因と、環境別の正しい対処を整理します。

CORSエラーが起きる「3つの場所」を特定する

まず最初にやることは、エラーが起きている場所を特定することです。CORSエラーは「フロントエンド → サーバー」の境界すべてで起きうるため、どの境界で詰まっているかを絞り込まないと対処が的外れになります。

ブラウザのDevToolsを開き、Networkタブでエラーになっているリクエストを確認してください。リクエストの宛先URLを見ることで、以下の3パターンのどれかに当てはまります。

パターン1: フロントエンドから外部APIを直接呼んでいる

Access to fetch at 'https://api.stripe.com/...' from origin 'http://localhost:3000' has been blocked by CORS policy というエラーです。Stripe・Twilio・SendGrid・Google Maps APIなど、プロバイダーが設定したCORSポリシーにフロントから直接当たっています。

パターン2: 同一アプリ内のAPIエンドポイントが別ポートで動いている

Access to fetch at 'http://localhost:8080/api/...' from origin 'http://localhost:3000' has been blocked by CORS policy というエラーです。フロントと同一ドメインのつもりが、ポートが異なるためにブラウザがクロスオリジンと判断しています。

パターン3: サーバーレス環境のレスポンスにCORSヘッダーがない

No 'Access-Control-Allow-Origin' header is present on the requested resource というエラーです。Cloudflare Workers・Firebase Functions・Supabase Edge Functionsのレスポンスにヘッダーが設定されていないケースです。

パターン1の解決: 外部APIはサーバー経由で呼ぶ

外部APIをフロントエンドから直接呼ぶことは、CORS問題だけでなくAPIキーの漏洩リスクも伴います。原則として、外部APIの呼び出しはサーバーサイドを経由させるべきです。

Next.js App RouterのRoute Handlerを使った中継の例です:

// app/api/send-email/route.ts
import { NextResponse } from "next/server";
 
export async function POST(request: Request) {
  const body = await request.json();
 
  // サーバーサイドからSendGrid APIを呼ぶ(CORSの問題なし)
  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: "送信に失敗しました" }, { status: 500 });
  }
 
  return NextResponse.json({ success: true });
}

フロントエンドからは /api/send-email というローカルのエンドポイントを呼ぶだけになり、CORSは発生しません。

Antigravityにこのパターンで修正を依頼するときは以下のように伝えると精度が上がります:

現在、フロントエンドから直接 SendGrid API を呼んでいます。
これをNext.js Route Handler を経由する形にリファクタリングしてください。
APIキーは環境変数 SENDGRID_API_KEY から読み込みます。
エンドポイントは /api/send-email で、フロントからは fetch('/api/send-email', {...}) で呼び出します。

パターン2の解決: Next.jsのrewritesでプロキシ設定する

開発中にフロントとAPIサーバーが別ポートで動いているケースでは、Next.jsのrewrites設定が最もシンプルな解決策です。

// 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;

この設定により、フロントから /api/backend/users を呼ぶとNext.jsが http://localhost:8080/api/users にプロキシしてくれます。ブラウザから見ると同一オリジンへのリクエストになるため、CORSは発生しません。

開発環境のみに限定しているのは意図的です。本番環境でこの設定が意図せず動くと、外部からのリクエストがバックエンドに通り抜けてしまう可能性があります。

パターン3の解決: サーバーサイドにCORSヘッダーを追加する

Cloudflare WorkersやFirebase Functionsなどのサーバーレス環境では、レスポンスに明示的にCORSヘッダーを設定する必要があります。

Cloudflare Workersの場合:

// worker.ts
export default {
  async fetch(request: Request): Promise<Response> {
    const origin = request.headers.get("Origin") ?? "";
 
    // プリフライトリクエスト(OPTIONSメソッド)への対応
    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,
      },
    });
  },
};

開発中は origin をそのまま返す形でもよいですが、本番では許可するオリジンを限定することをおすすめします:

const ALLOWED_ORIGINS = [
  "https://myapp.com",
  "https://www.myapp.com",
];
 
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];

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" },
  });
});

Antigravityに依頼する際は、どのサーバーレス環境を使っているかを明示してください:

Cloudflare Workers の worker.ts に CORS 設定を追加してください。
許可するオリジンは https://myapp.com のみです。
GET と POST メソッドを許可し、プリフライトリクエスト(OPTIONSメソッド)にも対応してください。

よくある見落とし: OPTIONSメソッドへの対応

CORSヘッダーを設定したのに解決しないときは、プリフライトリクエスト(OPTIONSメソッド)の対応漏れを疑ってください。

ブラウザは Content-Type: application/jsonAuthorization ヘッダーを含むリクエストを送る前に、OPTIONSメソッドで事前確認を行います。サーバーがOPTIONSに対して適切に応答していないと、Access-Control-Allow-Origin を設定していても動きません。

DevToolsのNetworkタブで、エラーになったリクエストの直前にOPTIONSメソッドのリクエストがあるかどうかを確認してみてください。OPTIONSリクエストが ERR_CONNECTION_REFUSED400 Bad Request になっていれば、そこが詰まっている場所です。

Antigravityに修正を依頼するときは「プリフライトリクエストにも対応してください」と明示することで、OPTIONSメソッドの処理も含めたコードが生成されます。

本番環境で再発させないために

開発中にCORSを解消できても、本番デプロイ後に再発するケースがあります。よくあるのは以下のパターンです:

開発中は localhost からアクセスしていたが、本番では別ドメインから呼ぶようになった、というのが最も多いです。許可オリジンを環境変数で管理しておくと、ステージング・本番で柔軟に切り替えられます:

// 環境変数で許可オリジンを管理
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS ?? "http://localhost:3000")
  .split(",")
  .map((o) => o.trim());

もう一つは、CloudflareのCache設定がCORSヘッダーを含むレスポンスをキャッシュしてしまうケースです。異なるオリジンから来たリクエストに、別オリジン向けのキャッシュが返されると、CORSエラーになります。CORSヘッダーを含むレスポンスはキャッシュしない設定を入れておくとトラブルを防げます。

個人でアプリ開発を続けてきた経験から言うと、CORSトラブルは一度パターンを理解してしまえば怖くありません。「どこの境界で何が起きているか」を特定することさえできれば、解決策は限られています。Antigravityへの指示もそのパターンに沿って伝えることで、的確なコードが返ってくるようになります。

同じような詰まりを抱えている方の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-06-19
Managed Agents API でバックグラウンドタスクを安全に走らせる設計
Antigravity 2.0 の Managed Agents API は、単一の API 呼び出しで隔離された Linux 環境にエージェントを起動し、推論・ツール実行・コード実行までを任せられます。便利な反面、放置すると暴走やコスト超過を招きます。バックグラウンド常駐タスクとして安全に運用する設計を考えます。
アプリ開発2026-05-17
AntigravityにRecyclerViewクラッシュを直させると別の場所が壊れる問題 — 防御的コピーで根本解決した話
RecyclerViewのIndexOutOfBoundsExceptionをAntigravityに修正させても再発する場合、共有リスト参照が原因の可能性があります。防御的コピーをAntigravityに正確に伝えるプロンプトとAGENTS.mdの書き方を解説します。
アプリ開発2026-07-13
画像の一括最適化をエージェントに任せる前に置く、見た目の劣化を止めるゲート
壁紙アプリの数百枚のアセットをエージェントに一括再エンコードさせたら、数枚だけ色がくすんでいました。サイズ削減率だけでは劣化を見抜けません。SSIM・ΔE・ファイルサイズの3軸でマージ前に不良変換を弾くゲートの設計と、Pillow と scikit-image で動く判定スクリプトをまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →