Store the path, never the signed URL. Signed URLs are meant to be generated at render time.
- Save
recipes.image_path = 'user-id/abc.jpg'. - On the page, sign them in one call:
supabase.storage.from('photos').createSignedUrls(paths, 3600)returns all URLs in one request, no N+1.
On the public-bucket question: a public bucket makes files readable by anyone who has the URL. It does not let anyone list files, listing still goes through the RLS policies on storage.objects. If recipe photos aren't private anyway (they're on a public recipe page), a public bucket with random file names is the simplest option and caches better on the CDN.