Docs.

Media Uploads — Supabase Storage

operations/media-uploads.md

All project / team / stills photography lives in the media bucket of the studio.chat Supabase project (pstvpdcqmsodirmrvzam). Bucket is public-read, service-role write, 50 MB per file, accepts JPEG / PNG / WebP / AVIF / MP4.

Folder conventions

media/
├── projects/<slug>/
│   ├── cover.jpg                  # the hero shot used on the catalog card and detail hero
│   ├── gallery-01.jpg
│   ├── gallery-02.jpg
│   └── …
├── team/<slug>/
│   └── photo.jpg                  # square or 4:5 portrait
├── stills/
│   └── 2026-04-shoot-name-01.jpg  # free-form; date-prefix recommended
└── kits/<slug>/
    └── hero.jpg

<slug> matches the MDX file basename (e.g. content/projects/tropical.en.mdxmedia/projects/tropical/cover.jpg).

Uploading

Option A — Supabase dashboard (simplest, no code)

  1. Open https://supabase.com/dashboard/project/pstvpdcqmsodirmrvzam/storage/buckets/media
  2. Create the subfolder (e.g. projects/lost-in-vegas-bts) if it doesn't exist.
  3. Drag-drop the image. Make sure it's at least 1600 px on the long side so next/image can serve responsive sizes without upscaling.
  4. Click the file → Copy URL. URLs look like: https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/projects/<slug>/cover.jpg
  5. Paste that URL into the cover field of the project's MDX file (or the photo field on the team JSON, etc).

Option B — CLI from the repo root

# Single file
supabase storage cp ./local-photo.jpg ss:///media/projects/my-slug/cover.jpg

# Whole folder
supabase storage cp -r ./shoot-folder ss:///media/stills/

Referencing in content

The site's media resolver (src/lib/media.ts) understands three forms:

  1. Full Supabase URL — paste as-is in MDX frontmatter cover: or gallery:
  2. /images/... path — looks under public/images/ locally (legacy)
  3. Empty / missing — auto-renders an SVG placeholder

So this is valid:

---
title: "Lost In Vegas — BTS"
cover: "https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/projects/lost-in-vegas-bts/cover.jpg"
---

Image specs

SurfaceAspect ratioMin size
Project cover16:10 (landscape)1600×1000
Project galleryflexible1600 px long side
Team portrait4:5 (portrait)1200×1500
Stillflexible1600 px long side
Kit hero16:10 (landscape)1600×1000

next/image resizes and serves AVIF/WebP automatically; just upload the original at reasonable resolution and let the framework handle the rest.

Local dev: images live on prod, not in local storage

supabase db reset --local wipes the local DB and re-applies migrations + seed.sql, but never touches the storage bucket. The local Supabase stack's storage layer (http://127.0.0.1:54321/storage/...) starts empty and stays empty.

What survives the reset is just the URL strings in the DB. Every items.cover_image, items.gallery, items.review_images, projects.cover, etc. is a hardcoded https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/… URL pointing at the prod project's media bucket. When you load a page locally, the browser fetches every image directly from prod (the bucket is public-read), and next/image re-serves it through the local dev server with AVIF/WebP optimization.

This works fine for normal local dev because:

  • prod media is public, so no auth dance
  • local DB carries the URLs in items dumped from prod via the MCP

But it has two consequences worth knowing:

  1. Offline dev breaks images. No connection to pstvpdcqmsodirmrvzam.supabase.co means every image is a broken image icon. The DB seeds fine; the pages render; just the <img> tags fail.
  2. Rotating prod storage breaks local. If the project is re-keyed, the bucket is renamed, or media files are deleted, every local dev env sees broken images on the next reload. The fix is to update the URLs in the seed (re-dump from prod via MCP) and db reset again.

If either becomes a real problem, the answer is to mirror the bucket locally on reset:

# One-off: copy the whole media bucket from prod into the local stack
supabase storage cp -r ss:///media ./tmp/media     # download from prod
# (Optional) point at local Supabase, then upload
SUPABASE_URL=http://127.0.0.1:54321 \
SUPABASE_SERVICE_ROLE_KEY=<local key> \
  supabase storage cp -r ./tmp/media ss:///media

Not automated — by default local dev relies on prod storage being reachable, which it almost always is.