6.7 KiB
6.7 KiB
| name | description | parent-skill | version | updated |
|---|---|---|---|---|
| storage-cdn | File storage with image transformations and CDN delivery | moai-platform-supabase | 1.0.0 | 2026-01-06 |
Storage and CDN Module
Overview
Supabase Storage provides file storage with automatic image transformations, CDN delivery, and fine-grained access control through storage policies.
Basic Upload
Upload File
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
async function uploadFile(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
return data.path
}
Upload with User Context
async function uploadUserFile(file: File, userId: string) {
const fileName = `${userId}/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('user-files')
.upload(fileName, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
return data
}
Image Transformations
Get Transformed URL
async function uploadImage(file: File, userId: string) {
const fileName = `${userId}/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('images')
.upload(fileName, file, { cacheControl: '3600', upsert: false })
if (error) throw error
// Get original URL
const { data: { publicUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName)
// Get resized URL
const { data: { publicUrl: resizedUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName, {
transform: { width: 800, height: 600, resize: 'contain' }
})
// Get thumbnail URL
const { data: { publicUrl: thumbnailUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName, {
transform: { width: 200, height: 200, resize: 'cover' }
})
return { originalPath: data.path, publicUrl, resizedUrl, thumbnailUrl }
}
Transform Options
Available transformation parameters:
- width: Target width in pixels
- height: Target height in pixels
- resize: 'cover' | 'contain' | 'fill'
- format: 'origin' | 'avif' | 'webp'
- quality: 1-100
Example Transforms
// Square thumbnail with crop
const thumbnail = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 150, height: 150, resize: 'cover' }
})
// WebP format for smaller size
const webp = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 800, format: 'webp', quality: 80 }
})
// Responsive image
const responsive = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 400, resize: 'contain' }
})
Bucket Management
Create Bucket
-- Via SQL
INSERT INTO storage.buckets (id, name, public)
VALUES ('images', 'images', true);
Bucket Policies
-- Allow authenticated users to upload to their folder
CREATE POLICY "User upload" ON storage.objects
FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'user-files' AND (storage.foldername(name))[1] = auth.uid()::text);
-- Allow public read on images bucket
CREATE POLICY "Public read" ON storage.objects
FOR SELECT
TO public
USING (bucket_id = 'images');
-- Allow users to delete their own files
CREATE POLICY "User delete" ON storage.objects
FOR DELETE
TO authenticated
USING (bucket_id = 'user-files' AND (storage.foldername(name))[1] = auth.uid()::text);
Download Files
Get Signed URL
For private buckets:
async function getSignedUrl(bucket: string, path: string, expiresIn: number = 3600) {
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(path, expiresIn)
if (error) throw error
return data.signedUrl
}
Download File
async function downloadFile(bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.download(path)
if (error) throw error
return data // Blob
}
File Management
List Files
async function listFiles(bucket: string, folder: string) {
const { data, error } = await supabase.storage
.from(bucket)
.list(folder, {
limit: 100,
offset: 0,
sortBy: { column: 'created_at', order: 'desc' }
})
if (error) throw error
return data
}
Delete File
async function deleteFile(bucket: string, paths: string[]) {
const { data, error } = await supabase.storage
.from(bucket)
.remove(paths)
if (error) throw error
return data
}
Move/Rename File
async function moveFile(bucket: string, fromPath: string, toPath: string) {
const { data, error } = await supabase.storage
.from(bucket)
.move(fromPath, toPath)
if (error) throw error
return data
}
React Integration
Upload Component
function FileUploader({ bucket, onUpload }: Props) {
const [uploading, setUploading] = useState(false)
async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
setUploading(true)
try {
const path = `${Date.now()}-${file.name}`
const { error } = await supabase.storage
.from(bucket)
.upload(path, file)
if (error) throw error
onUpload(path)
} finally {
setUploading(false)
}
}
return (
<input
type="file"
onChange={handleUpload}
disabled={uploading}
/>
)
}
Image with Fallback
function StorageImage({ path, bucket, width, height, fallback }: Props) {
const { data: { publicUrl } } = supabase.storage
.from(bucket)
.getPublicUrl(path, {
transform: { width, height, resize: 'cover' }
})
return (
<img
src={publicUrl}
alt=""
onError={(e) => { e.currentTarget.src = fallback }}
width={width}
height={height}
/>
)
}
Best Practices
File Organization:
- Use user ID as folder prefix for user content
- Include timestamp in filenames to prevent collisions
- Use consistent naming conventions
Performance:
- Set appropriate cache-control headers
- Use image transformations instead of storing multiple sizes
- Leverage CDN for global delivery
Security:
- Always use RLS-style policies for storage
- Use signed URLs for private content
- Validate file types before upload
Context7 Query Examples
For latest Storage documentation:
Topic: "supabase storage upload download" Topic: "storage image transformations" Topic: "storage bucket policies"
Related Modules:
- row-level-security.md - Storage access policies
- typescript-patterns.md - Client patterns