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 の問題と誤認されます。
診断手順は以下の通りです:
- Supabase ダッシュボードの「Edge Functions」→「Logs」を開く
- 呼び出したタイミングのログを確認する
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 のデータが取得できない・表示されないときの原因別対処法が参考になります。
同じ問題で詰まっている方の参考になれば幸いです。