🎸
🚀 Beta läuft
PYNGUP: Rebellion gegen toxische Produktivität
Beta auf 100 Plätze begrenzt. Tasks werden zu sozialen Commitments statt einsamer To-Dos.
CORS-Fehler gehören zu den frustrierendsten Problemen beim Entwickeln mit Supabase Edge Functions. Sie funktionieren perfekt in Postman oder Insomnia, aber sobald du sie aus dem Browser aufrufst, bekommst du diese kryptische Fehlermeldung:
Access to fetch at 'https://deinprojekt.supabase.co/functions/v1/deine-funktion'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
In diesem Artikel zeige ich dir genau, wie du CORS in Supabase Edge Functions richtig konfigurierst - mit funktionierenden Code-Beispielen und häufigen Fallstricken.
Anders als bei Supabase's REST API musst du CORS manuell in Edge Functions handhaben. Das liegt daran, dass Edge Functions vollständig anpassbare Server-Funktionen sind, die du selbst kontrollierst.
Wichtig: Supabase stellt keine automatische CORS-Konfiguration für Edge Functions bereit!
Erstelle zuerst eine wiederverwendbare CORS-Konfiguration. Ich empfehle, eine _shared/cors.ts Datei anzulegen:
// supabase/functions/_shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
}
Das ist der wichtigste Teil: Der OPTIONS-Check muss ganz oben in deiner Funktion stehen:
// supabase/functions/deine-funktion/index.ts
import { corsHeaders } from '../_shared/cors.ts'
Deno.serve(async (req) => {
// DIESER CHECK MUSS GANZ OBEN STEHEN!
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
// Deine eigentliche Funktionslogik hier
try {
const data = await req.json()
// Verarbeitung...
return new Response(
JSON.stringify({ success: true, data }),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
}
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 400,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
}
)
}
})
Browser senden einen Preflight-Request (OPTIONS) bevor sie den eigentlichen Request ausführen. Dieser Preflight fragt: "Darf ich diese Anfrage stellen?"
Wenn dein OPTIONS-Handler nicht als erstes kommt, könnte deine Funktion einen Fehler werfen, bevor sie überhaupt die CORS-Headers senden kann.
Falsch:
if (error) {
return new Response('Error', { status: 500 })
// Keine CORS Headers!
}
Richtig:
if (error) {
return new Response('Error', {
status: 500,
headers: corsHeaders // CORS Headers auch bei Fehlern!
})
}
Stelle sicher, dass deine Headers alle nötigen Werte enthalten:
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type, x-requested-with',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, PATCH',
'Access-Control-Max-Age': '86400', // Cache Preflight für 24h
}
Sicherheitsproblem:
// NIEMALS in Produktion!
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true'
Besser für Produktion:
const allowedOrigins = [
'https://deineapp.com',
'https://www.deineapp.com',
'http://localhost:3000' // nur für Development
]
const origin = req.headers.get('origin')
const corsOrigin = allowedOrigins.includes(origin) ? origin : 'null'
const corsHeaders = {
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Credentials': 'true',
// ...weitere headers
}
Hier ist eine komplette Edge Function mit korrekter CORS-Behandlung:
// supabase/functions/user-profile/index.ts
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { corsHeaders } from '../_shared/cors.ts'
interface ProfileRequest {
userId: string
name: string
email: string
}
Deno.serve(async (req) => {
// CORS Preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// Supabase Client initialisieren
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
// Request Body parsen
const { userId, name, email }: ProfileRequest = await req.json()
// Validierung
if (!userId || !name || !email) {
return new Response(
JSON.stringify({
error: 'userId, name und email sind erforderlich'
}),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Database Operation
const { data, error } = await supabase
.from('profiles')
.upsert({ user_id: userId, name, email })
.select()
if (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Erfolgreiche Antwort
return new Response(
JSON.stringify({ success: true, profile: data[0] }),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
} catch (error) {
return new Response(
JSON.stringify({ error: 'Unbekannter Fehler aufgetreten' }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
})
So rufst du die Funktion korrekt aus deinem Frontend auf:
// React/Next.js Beispiel
const updateProfile = async (userData) => {
try {
const response = await fetch(
'https://dein-projekt.supabase.co/functions/v1/user-profile',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${supabase.auth.session()?.access_token}`,
'apikey': 'dein-anon-key'
},
body: JSON.stringify(userData)
}
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
return result
} catch (error) {
console.error('Profile update failed:', error)
throw error
}
}
Öffne die Network-Registerkarte und schaue dir sowohl den OPTIONS- als auch den POST-Request an:
# OPTIONS Request testen
curl -X OPTIONS \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type" \
https://dein-projekt.supabase.co/functions/v1/deine-funktion
# Erwartete Antwort sollte CORS Headers enthalten
Firefox: Manchmal musst du den Browser komplett neustarten, wenn CORS-Probleme auftreten. Firefox kann WebSocket-Verbindungen nicht korrekt aufräumen.
Safari: Besonders streng bei CORS-Policies. Stelle sicher, dass alle Headers exakt korrekt sind.
Für lokale Tests mit dem Supabase CLI:
# Edge Functions lokal starten
supabase functions serve --debug
# Deine Funktion ist dann verfügbar unter:
# http://localhost:54321/functions/v1/deine-funktion
Falls du immer noch CORS-Probleme hast, prüfe diese Punkte:
Für Anwendungen mit mehreren Domains:
const getDynamicCorsHeaders = (request: Request) => {
const origin = request.headers.get('origin')
// Definiere deine erlaubten Origins
const allowedOrigins = [
'https://app.deineseite.com',
'https://admin.deineseite.com',
...(Deno.env.get('ENVIRONMENT') === 'development'
? ['http://localhost:3000', 'http://127.0.0.1:3000']
: [])
]
const corsOrigin = allowedOrigins.includes(origin) ? origin : null
return {
'Access-Control-Allow-Origin': corsOrigin || 'null',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials': 'true',
}
}
// Verwende in deiner Funktion:
Deno.serve(async (req) => {
const corsHeaders = getDynamicCorsHeaders(req)
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
// ... Rest deiner Funktion
})
Manchmal können auch andere Faktoren CORS-Probleme verursachen:
// Überprüfe auch diese Headers
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type, x-requested-with, cache-control',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, PATCH',
'Access-Control-Max-Age': '86400',
'Access-Control-Expose-Headers': 'content-length', // Wichtig für manche Clients
}
Verwende verschiedene CORS-Konfigurationen je nach Umgebung:
const isDevelopment = Deno.env.get('ENVIRONMENT') === 'development'
export const corsHeaders = {
'Access-Control-Allow-Origin': isDevelopment ? '*' : 'https://deineapp.com',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials': isDevelopment ? 'false' : 'true',
}
CORS in Supabase Edge Functions ist zunächst verwirrend, aber mit der richtigen Implementierung unproblematisch. Die wichtigsten Punkte:
* mit Credentials in Produktion verwendenMit diesen Beispielen sollten deine CORS-Probleme der Vergangenheit angehören. Falls du trotzdem Probleme hast, überprüfe die Reihenfolge deines Codes - der OPTIONS-Check muss wirklich als allererstes kommen!
Hast du weitere CORS-Probleme mit Supabase? Schreib einen Kommentar und ich helfe gerne weiter!
Nikolai Fischer ist Gründer von Kommune3 (seit 2007) und führender Experte für die Verbindung von Software-Entwicklung und Unternehmertum. Mit 17+ Jahren Erfahrung hat er hunderte von Projekten geleitet und erreichte #1 auf Hacker News. Als Host des Podcasts "Kommit mich" und Gründer von skillution verbindet er technische Expertise mit unternehmerischem Denken. Seine Artikel über moderne Webentwicklung und systematisches Problem-Solving haben tausende von Entwicklern beeinflusst.
Folge Niko auf:
Comments