◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-11中級

「CORSエラー」と表示されたとき、実は別の問題が隠れている — Rork + Supabase Edge Functions

RorkアプリでSupabase Edge FunctionsをAPIとして使うとCORSエラーが出る問題を解説。React Nativeの仕組み上、実は本当のCORSエラーは起きにくく、別の根本原因が隠れていることがほとんどです。

Supabase37Edge FunctionsCORSトラブルシューティング85React Native239

Supabase Edge Functions を呼び出したら Access-Control-Allow-Origin エラーが出た、という相談を受けるたびに、実際のエラーログを確認すると「これは CORS の問題ではなかった」というケースに何度も遭遇してきました。

2014年からアプリ開発を続けてきた経験でいうと、「CORS エラー」という表現はかなり広い意味で使われていて、ネットワーク越しの API 呼び出しで何かがうまくいかないと、すべて CORS のせいにされてしまうことがあります。Rork + Supabase の組み合わせでも、同じ構造のすれ違いが起きやすいです。

React Native アプリでは、本来 CORS は発動しない

まず前提として確認しておきたいのが、CORS(Cross-Origin Resource Sharing)はブラウザのセキュリティ機能だということです。

ブラウザは https://example.com で動くページから https://api.another.com に fetch を投げると、オリジンが違うとして CORS のチェックを行います。これは「ユーザーが知らないうちに別サービスに情報を送りつけられる」攻撃を防ぐための仕組みです。

React Native(Rork のベースとなっているフレームワーク)のアプリは、iOS や Android のネイティブランタイム上で動いています。ブラウザではありません。そのため、スマートフォンアプリとして実機や TestFlight 上で動かしている限り、CORS の制約は原理上かかりません。

// React Native では以下の fetch に CORS 制約はかからない
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { message: 'hello' }
})

では、なぜ「CORS エラー」のような症状が出るのでしょうか。以下の 3 パターンで原因を整理してみます。

パターン1 — Rork Companion(ブラウザプレビュー)でテストしているケース

Rork の開発中に PC ブラウザ上の Companion でプレビューすると、そのプレビューはブラウザ環境で動作します。この場合に限っては、本物の CORS 制約が発動します。

スマートフォンの実機ではまったく問題なかったのに、ブラウザプレビューだけでエラーになる場合は、Edge Function 側に CORS ヘッダーを追加するだけで解消します。

// supabase/functions/my-function/index.ts
 
const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers':
    'authorization, x-client-info, apikey, content-type',
}
 
Deno.serve(async (req) => {
  // OPTIONS(プリフライトリクエスト)への応答
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }
 
  try {
    const { message } = await req.json()
    const result = { response: `Received: ${message}` }
 
    return new Response(JSON.stringify(result), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  } catch (error) {
    // エラーレスポンスにも必ず corsHeaders を付ける
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 400,
    })
  }
})

ポイントは 2 つです。OPTIONS リクエスト(プリフライト)への応答と、エラーレスポンスを含むすべてのレスポンス に corsHeaders を付けること。エラーレスポンスに CORS ヘッダーが付いていないと、ブラウザはエラーの内容を読めずに CORS エラーとして表示してしまいます。

パターン2 — Edge Function が 500 エラーでクラッシュしているケース

実機での動作確認中に CORS 的な症状が出る場合、Edge Function 自体がエラーで落ちていることがほとんどです。これが最も見落とされやすいケースです。

Supabase の Edge Function が未ハンドルの例外でクラッシュすると、Deno ランタイムはデフォルトのエラーレスポンスを返しますが、このとき CORS ヘッダーが付いていないことがあります。ブラウザプレビューで見ると「CORS エラー」として表示されるため、CORS の問題と誤認されます。

診断手順は以下の通りです:

  1. Supabase ダッシュボードの「Edge Functions」→「Logs」を開く
  2. 呼び出したタイミングのログを確認する
  3. Error や Uncaught が含まれていれば、CORS ではなく Edge Function 自体のバグ
// よくある失敗パターン: try-catch を忘れている
Deno.serve(async (req) => {
  const { userId } = await req.json()  // ← ここで例外が起きるとクラッシュ
  const user = await getUser(userId)    // ← DB エラーでもクラッシュ
  return new Response(JSON.stringify(user))
})
 
// 修正版: try-catch で必ず捕捉し、エラーにも corsHeaders を付ける
Deno.serve(async (req) => {
  const corsHeaders = { 'Access-Control-Allow-Origin': '*' }
 
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }
 
  try {
    const body = await req.json()
    if (!body.userId) throw new Error('userId is required')
    const user = await getUser(body.userId)
    return new Response(JSON.stringify(user), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  } catch (error) {
    console.error('Function error:', error.message)
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 400,
    })
  }
})

パターン3 — 認証ヘッダーの設定ミスで 401 になっているケース

Supabase の supabase.functions.invoke() を使わず、生の fetch で Edge Function を呼び出している場合、Authorization ヘッダーが正しく設定されていないと 401 エラーになります。この 401 エラーが CORS ヘッダーなしで返ってくると、ブラウザプレビューでは CORS エラーのように見えます。

// ❌ 問題: Authorization ヘッダーが未設定
const response = await fetch(
  'https://xxxxx.supabase.co/functions/v1/my-function',
  {
    method: 'POST',
    body: JSON.stringify({ message: 'hello' }),
  }
)
 
// ✅ supabase-js のクライアントを使うと自動的にヘッダーが付く
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { message: 'hello' },
})

supabase.functions.invoke() を使うと、プロジェクトの ANON_KEY と Authorization ヘッダーが自動で付与されます。生の fetch を使う場合は、以下のヘッダーが必要です:

const ANON_KEY = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY
 
const response = await fetch(
  'https://xxxxx.supabase.co/functions/v1/my-function',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${ANON_KEY}`,
      'apikey': ANON_KEY,
    },
    body: JSON.stringify({ message: 'hello' }),
  }
)

CORS エラーに見える症状の診断フロー

3 つのパターンをまとめると、以下の順序で確認するとほとんどのケースで原因を特定できます。

ステップ 1: 実機(または iOS シミュレーター)でも同じエラーが出るか確認する

  • 実機で出ない → Rork Companion(ブラウザ)固有の問題 → Edge Function に CORS ヘッダーを追加
  • 実機でも出る → CORS の問題ではない、次へ

ステップ 2: Supabase ダッシュボードのログを確認する

  • Edge Function のエラーログがある → 関数自体のバグを修正(try-catch の追加)
  • ログが何も出ていない → 関数が呼び出されていない(URL か認証の問題)

ステップ 3: supabase.functions.invoke() を使っているか確認する

  • 生の fetch を使っている → supabase.functions.invoke() に変更するか、必要なヘッダーを手動で付ける

Supabase の Edge Functions は仕組みとしては非常に素直で、ログを丁寧に読むとほぼ必ず原因が見つかります。「CORS エラー」と表示されても、すぐに CORS の設定を変えるのではなく、まず実機テストとログ確認から始めると、遠回りせずに解決できます。

Supabase Edge Functions の詳細な設計パターンについては、Rork Max × Supabase Edge Functions & Row Level Security — セキュアな API 設計完全ガイドも合わせてご覧ください。データが取得できないといった別の Supabase トラブルについては、Rork アプリで Supabase のデータが取得できない・表示されないときの原因別対処法が参考になります。

同じ問題で詰まっている方の参考になれば幸いです。

シェア

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

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

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

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

関連記事

⬡ 開発ツール2026-05-18
Rork で Linking.canOpenURL が iOS だけ false を返す ─ LSApplicationQueriesSchemes 設定漏れの直し方
TikTok や X、LINE 公式アカウントへ飛ばす実装が iOS の本番ビルドだけで動かない症状を、LSApplicationQueriesSchemes の追加と EAS Build の再生成手順で確実に解消します。
⬡ 開発ツール2026-04-22
Rorkで作ったアプリの日本語入力が『変換中に暴発する』問題を根本解決する
Rorkで生成したアプリの日本語入力で、IME変換中にイベントが暴発する、候補ウィンドウが閉じる問題を根本から解決します。デバウンスでの吸収、Enter確定パターン、multilineでの分離という3つの解決策と、Rorkへ追加指示するプロンプト例を紹介します。
⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。