Supabase Storage in der Praxis: Upload, Berechtigungen und CDN-Beschleunigung

Ich starre auf die Fehlermeldung in der Konsole. Die Avatar-Upload-Funktion ist seit einer halben Stunde live – und schon melden Nutzer: Alle Avatare zeigen plötzlich dasselbe Gesicht.
Die Ursache: fehlende RLS-Policies für Storage. Der Bucket war öffentlich, Upload-Pfade ohne Nutzerisolation – jeder Upload konnte fremde Dateien überschreiben. Ein Berechtigungsfehler, der beinahe ein Produktionsvorfall geworden wäre.
Supabase Storage wirkt einfach – aber Berechtigungen, CDN-Beschleunigung und Bildtransformation haben viele Fallstricke. Dieser Artikel fasst die Erfahrungen und Lessons Learned zusammen.
1. Schnellstart: Standard-Datei-Upload
Zuerst das Grundlegende: Dateien hochladen.
Bucket erstellen
Öffnen Sie die Supabase-Konsole, wählen Sie links Storage und klicken Sie auf New bucket. Vergeben Sie einen Namen – z. B. avatars für Profilbilder, posts für Artikelbilder. Die Option Make this bucket public? lassen Sie vorerst deaktiviert; im Berechtigungskapitel erklären wir den Unterschied.
Meine Regel: Private Buckets für sensible Dateien, Public Buckets für statische Assets. Standardmäßig erstelle ich private Buckets und passe bei Bedarf an.
SDK-Upload-Code
Vorausgesetzt, @supabase/supabase-js ist installiert – der Code ist überschaubar:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
)
// Datei hochladen
async function uploadFile(file: File) {
const filePath = `uploads/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('avatars') // Bucket-Name
.upload(filePath, file, {
cacheControl: '3600', // 1 Stunde Cache
upsert: false // Fehler, wenn Datei existiert – kein Überschreiben
})
if (error) {
console.error('Upload fehlgeschlagen:', error.message)
return null
}
return data.path // Dateipfad zurückgeben
}
Ehrlich gesagt habe ich diesen Code mindestens zehnmal geschrieben. Entscheidend ist das Design von filePath – später erklären wir den Zeitstempel-Prefix und die Nutzerisolation.
Dateigrößen-Limits
Laut Dokumentation unterstützt Standard-Upload bis zu 5 GB. In der Praxis: Unter 6 MB ist Standard-Upload am besten; darüber empfiehlt sich TUS mit Resume.
Was ist TUS? Kurz gesagt: Protokoll für große Uploads mit Fortsetzung nach Unterbrechung. Netz weg – nach Reconnect geht es weiter, ohne von vorn zu starten. Für Videos und große Bilder ein deutlicher UX-Gewinn – stellen Sie sich 90 % Fortschritt und dann Verbindungsabbruch vor.
TUS erfordert zusätzliche Konfiguration. Wenn Sie es noch nicht brauchen, reicht Standard-Upload für die meisten Fälle.
// TUS-Upload-Beispiel (empfohlen für große Dateien)
const { data, error } = await supabase.storage
.from('videos')
.upload('large-video.mp4', file, {
duplex: 'half', // Streaming-Upload aktivieren
// TUS übernimmt Resume automatisch
})
2. Sichere Konfiguration: RLS-Policies im Detail
Zurück zu meinem Fehler um drei Uhr nachts – keine Berechtigungen, Dateien konnten beliebig überschrieben werden.
Supabase Storage nutzt wie die Datenbank PostgreSQL. Berechtigungen laufen über RLS (Row Level Security). Ein Bucket entspricht einer Tabelle, jede Datei einer Zeile.
Public vs. Private Bucket
Bei der Erstellung wählen Sie Public bucket oder Private bucket.
Public bucket: Lesbar für alle ohne Authentifizierung – geeignet für öffentliche Avatare, Logos, statische Assets.
Private bucket: Zugriff nur mit Authentifizierung. Wichtig: Authentifizierung allein reicht nicht – wer lesen, schreiben oder löschen darf, legen RLS-Policies fest.
Mein Rat: Standardmäßig Private Bucket. Erst Berechtigungen sauber setzen, dann bei Bedarf öffnen – sicherer als nachträglich absichern.
Arten von RLS-Policies
Auf der Policy-Seite von Storage gibt es vier Operationen:
- SELECT: Datei lesen (Download, URL abrufen)
- INSERT: Neue Datei hochladen
- UPDATE: Bestehende Datei aktualisieren/überschreiben
- DELETE: Datei löschen
Jede Operation lässt sich separat per Policy steuern. Typisches Muster:
-- Nutzer dürfen nur eigene Dateien verwalten
CREATE POLICY "Users manage own files"
ON storage.objects FOR ALL
USING (auth.uid()::text = (storage.foldername(name))[1]);
Das SQL wirkt komplex – aufgeschlüsselt:
auth.uid()liefert die ID des angemeldeten Nutzersstorage.foldername(name)extrahiert das erste Verzeichnis im Pfad- Bei
user123/avatar.jpgist das erste Segmentuser123
Die Policy erlaubt Operationen nur, wenn das erste Pfadsegment der Nutzer-ID entspricht – das ist die Kernidee der Nutzerisolation.
Nutzerisolation umsetzen
Beim Upload die Nutzer-ID als erstes Pfadsegment:
async function uploadAvatar(userId: string, file: File) {
// Pfad: Nutzer-ID/Dateiname
const filePath = `${userId}/avatar-${Date.now()}.jpg`
const { data, error } = await supabase.storage
.from('avatars')
.upload(filePath, file)
return data?.path
}
Jeder Nutzer landet in seinem „Ordner“. RLS erlaubt nur Pfade mit eigener ID – fremde Dateien sind tabu.
Signierte Zugriffs-URLs erzeugen
Dateien in Private Buckets liefern ohne Signatur 404. Lösung:
// Temporärer Link (1 Stunde gültig)
const { data, error } = await supabase.storage
.from('avatars')
.createSignedUrl('user123/avatar.jpg', 3600)
console.log(data?.signedUrl) // Vollständige signierte URL
Die Gültigkeitsdauer wählen Sie selbst. Zu lang ist riskant, zu kurz nervt. 1–4 Stunden sind ein guter Mittelweg.
Für öffentliche Dateien ohne Bucket-Umstellung: getPublicUrl:
const { data } = supabase.storage
.from('public-assets')
.getPublicUrl('logo.png')
// Diese URL braucht keine Signatur – für alle erreichbar
Häufige Policy-Fallstricke
Ein paar Stolpersteine aus der Praxis:
-
INSERT-Policy vergessen: Nutzer ist eingeloggt, Upload schlägt fehl – Fehler: „new row violates row-level security policy“
-
Policy zu weit gefasst:
USING (true)erlaubt allen alles – effektiv wie ohne RLS. -
Schlechtes Pfaddesign: Liegt die Nutzer-ID nicht im ersten Segment, liefert
foldernamedas falsche Ergebnis. Ich hatteuploads/user123/file.jpg– extrahiert wurdeuploads, die Policy griff ins Leere.
Policies zuerst im SQL-Editor der Konsole testen, dann in Produktion übernehmen.
3. Performance: Smart CDN und Bildtransformation
Upload und Berechtigungen stehen – als Nächstes: schnellere Auslieferung.
Smart CDN – Funktionsweise
Supabase Smart CDN ist kein klassisches CDN. Es passt die Cache-Strategie an die Zugriffshäufigkeit an: beliebte Dateien länger, selten genutzte kürzer.
Laut Dokumentation synchronisiert sich Cache-Invalidierung weltweit in maximal 60 Sekunden. Ein Update in Tokio ist in New York innerhalb einer Minute sichtbar – deutlich schneller als viele traditionelle CDNs mit Minuten oder Stunden.
Smart CDN ist kostenpflichtig – Pro Plan ($25/Monat). Im Free Plan funktionieren Upload und Download, aber ohne CDN – direkt von Supabase-Servern.
Bildtransformations-Parameter
Praktisches Feature: Skalierung und Zuschnitt per URL-Parameter, ohne eigene Bildpipeline.
Basisparameter:
?width=300&height=200 // Zielgröße
?resize=contain // Seitenverhältnis beibehalten, kein Zuschnitt
?resize=cover // Bereich füllen, überschüssige Teile abschneiden
?quality=80 // Qualität (1–100)
?format=webp // WebP – kleinere Dateigröße
Kombiniert:
const baseUrl = supabase.storage
.from('avatars')
.getPublicUrl('user123/avatar.jpg').data.publicUrl
// Thumbnail erzeugen
const thumbnailUrl = `${baseUrl}?width=100&height=100&resize=cover`
Limits der Bildtransformation:
- Größe: 1 bis 2500 Pixel
- Originaldatei: maximal 25 MB
- Formate: JPEG, PNG, WebP, GIF, AVIF
Bei Überschreitung gibt es Fehler. Ein 30-MB-Original zum Skalieren – bei mir abgelehnt.
Abrechnung: Freikontingent pro Projekt
Bildtransformation wird pro Transformation abgerechnet, nicht nach Speichervolumen.
Pro Projekt und Monat sind die ersten 100 Transformationen kostenlos. Danach $5 pro 1.000 Transformationen.
Für persönliche Projekte oder kleine Teams reichen 100 meist. Mein Blog braucht monatlich nur ein paar Dutzend Avatare und Beitragsbilder. Nur bei bildlastigen Apps à la Instagram lohnt genaueres Kalkulieren.
Next.js-Integration: Image Loader
Mit Next.js können Sie einen Supabase Image Loader konfigurieren – next/image hängt Transformationsparameter automatisch an:
// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './supabase-image-loader.js',
}
}
Loader-Datei:
// supabase-image-loader.js
export default function supabaseLoader({ src, width, quality }) {
const params = new URLSearchParams()
params.set('width', width.toString())
params.set('quality', (quality || 75).toString())
params.set('format', 'webp')
return `${src}?${params.toString()}`
}
Mit <Image src="..." width={300} /> in Next.js werden die Parameter automatisch gesetzt.
Pro-Plan-Schwelle
Smart CDN und Bildtransformation erfordern den Pro Plan. Free Plan: Basis-Upload und -Download.
Upgrade sinnvoll? Hängt vom Projekt ab. Für wenige Avatare reicht Free Plan. Bei vielen Bildern und Performance-Fokus spart Pro Plan CDN und Transformation – ohne eigene CDN- oder Bildverarbeitungs-Infrastruktur.
Mein Vorgehen: Vor dem Launch Free Plan zum Testen, nach stabilem Traffic Upgrade auf Pro. $25/Monat sind kein Kleingeld.
4. Praxisbeispiel: Vollständige Blog-Konfiguration
Theorie ist gut – hier die Storage-Konfiguration meines Blogs, von null bis produktionsreif.
Szenario: Nutzer-Avatare + Artikelbilder
Zwei Buckets:
avatars: Profilbilder, Private Bucket – Nutzer verwalten nur eigene Avatarepost-images: Artikelbilder, Private Bucket – Autoren laden hoch, Lesen für alle (per signierte URL)
Schritt 1: Buckets erstellen
In der Konsole:
- Storage > New bucket > Name
avatars, Private aktivieren - Gleiches für
post-images
Schritt 2: RLS-Policies konfigurieren
Policies für avatars:
-- Alle dürfen Avatare lesen (öffentliches Lesen)
CREATE POLICY "Anyone can view avatars"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');
-- Nutzer dürfen nur eigene Avatare hochladen und aktualisieren
CREATE POLICY "Users manage own avatar"
ON storage.objects FOR INSERT
WITH CHECK (bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1]);
-- Nutzer dürfen nur eigene Avatare löschen
CREATE POLICY "Users delete own avatar"
ON storage.objects FOR DELETE
USING (bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1]);
Policies für post-images:
-- Autoren dürfen Artikelbilder hochladen (Rolle author im JWT)
CREATE POLICY "Authors can upload post images"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'post-images'
AND auth.jwt() ->> 'role' = 'author'
);
-- Alle dürfen Artikelbilder lesen
CREATE POLICY "Public read post images"
ON storage.objects FOR SELECT
USING (bucket_id = 'post-images');
Schritt 3: Frontend-Upload-Code
Avatar-Upload-Komponente:
async function handleAvatarUpload(file: File) {
const user = await supabase.auth.getUser()
if (!user.data.user) return alert('Bitte zuerst anmelden')
// Pfad: Nutzer-ID/avatar.jpg (fester Name – jeder Upload überschreibt das alte Bild)
const filePath = `${user.data.user.id}/avatar.jpg`
const { error } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true })
if (!error) {
// Public URL (SELECT-Policy erlaubt Lesen für alle)
const url = supabase.storage.from('avatars').getPublicUrl(filePath)
setUserAvatar(url.data.publicUrl)
}
}
Artikelbild-Upload:
async function handlePostImageUpload(file: File) {
const filePath = `posts/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('post-images')
.upload(filePath, file)
if (!error) {
// Signierte URL, 24 Stunden gültig
const { data: urlData } = await supabase.storage
.from('post-images')
.createSignedUrl(filePath, 86400)
insertImageToEditor(urlData?.signedUrl)
}
}
Schritt 4: Tests vor dem Go-live
Vor dem Launch prüfen:
- Sehen nicht angemeldete Nutzer Artikelbilder? (Ja – SELECT-Policy erlaubt es)
- Können normale Nutzer Artikelbilder hochladen? (Nein – nur Rolle author)
- Kann Nutzer A den Avatar von Nutzer B überschreiben? (Nein – Pfadisolation)
Jeden Punkt einmal durchspielen. Die Lektion um drei Uhr nachts will ich nicht wiederholen.
Fazit
Bei Supabase Storage geht es um drei Dinge: Upload, Berechtigungen, Beschleunigung.
Upload ist am einfachsten – wenige Zeilen Code. Bei Berechtigungen lohnt Sorgfalt: RLS-Policies einmal konfigurieren reicht nicht – sie müssen zum Geschäftsmodell passen und getestet werden. CDN und Bildtransformation sind Extras im Pro Plan, sparen aber Entwicklungszeit.
Mein Ansatz: Zuerst Upload und Berechtigungen stabil und sicher. Bei Bedarf CDN, bei Bedarf Transformation. Schrittweise statt alles auf einmal.
Wenn Sie Supabase Storage nutzen – teilen Sie gern Ihre Fallstricke. Mit meiner Erfahrung um drei Uhr nachts bin ich vermutlich nicht allein.
Supabase Storage – vollständiger Konfigurationsablauf
Praxisleitfaden von Bucket-Erstellung über Berechtigungen bis zur CDN-Beschleunigung
⏱️ Estimated time: 30 min
- 1
Step 1: Bucket erstellen
Privaten Bucket in der Supabase-Konsole anlegen:
• Storage > New bucket
• Name eingeben (z. B. avatars)
• Private aktivieren (empfohlen als Standard)
• Create bucket klicken - 2
Step 2: RLS-Policy konfigurieren
Row Level Security für den Bucket einrichten:
• Storage > Bucket wählen > Policies
• New Policy klicken
• Operation wählen (SELECT/INSERT/UPDATE/DELETE)
• Policy-Regel schreiben (z. B. Nutzerisolation)
• Testen und in Produktion anwenden - 3
Step 3: Dateien hochladen
Upload per SDK:
• Pfadstruktur planen (z. B. userId/filename)
• storage.from().upload() aufrufen
• cacheControl und upsert setzen
• Fehler und Rückgabepfad behandeln - 4
Step 4: CDN und Bildtransformation (optional)
Mit Pro Plan verfügbar:
• Smart CDN cached beliebte Dateien automatisch
• Bildtransformation per URL-Parameter (width/height/format)
• Next.js Image Loader integrieren
• Freikontingent im Blick behalten (100 Bilder/Monat)
FAQ
Was ist der Unterschied zwischen Public und Private Bucket?
Wie konfiguriere ich RLS-Policies für Nutzerisolation?
• Upload-Pfad: userId/filename
• Policy: auth.uid()::text = (storage.foldername(name))[1]
• Nutzer dürfen nur Pfade mit eigener ID bearbeiten
Wie teile ich Dateien aus einem Private Bucket extern?
Welche Upload-Limits gibt es?
Welche Parameter und Limits hat die Bildtransformation?
• width/height: Größe (1–2500 Pixel)
• resize: contain (Seitenverhältnis) oder cover (Zuschnitt)
• quality: Qualität (1–100)
• format: webp/jpeg/png/gif/avif
Limit: Originaldatei maximal 25 MB
Kosten Smart CDN und Bildtransformation extra?
8 Min. Lesezeit · Veröffentlicht am: 9. Apr. 2026 · Aktualisiert am: 14. Juli 2026
Supabase in der Praxis
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Supabase Auth in der Praxis: E-Mail-Verifizierung, OAuth und Session-Management
Supabase Auth Praxisleitfaden: E-Mail-Verifizierung konfigurieren, OAuth integrieren, JWT-Session-Management und PKCE-Flow. Alles für die Benutzerauthentifizierung an einem Ort.
Teil 3 von 10
Nächster
Supabase Realtime in der Praxis: Drei Modi im Vergleich und kollaborative Apps
Supabase Realtime bietet drei Echtzeit-Modi: Postgres Changes, Presence und Broadcast. Dieser Artikel vergleicht die Modi, liefert vollständige Codebeispiele für kollaborative Apps und erklärt die RLS-Sicherheitskonfiguration.
Teil 5 von 10
Ähnliche Beiträge
Supabase Einstieg: PostgreSQL + Auth + Storage als All-in-One-Backend

Supabase Einstieg: PostgreSQL + Auth + Storage als All-in-One-Backend
Supabase-Datenbankdesign: Tabellenstruktur, Beziehungen und Row Level Security – vollständiger Leitfaden


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen