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.mdx
→ media/projects/tropical/cover.jpg).
Uploading
Option A — Supabase dashboard (simplest, no code)
- Open https://supabase.com/dashboard/project/pstvpdcqmsodirmrvzam/storage/buckets/media
- Create the subfolder (e.g.
projects/lost-in-vegas-bts) if it doesn't exist. - Drag-drop the image. Make sure it's at least 1600 px on the long side
so
next/imagecan serve responsive sizes without upscaling. - Click the file → Copy URL. URLs look like:
https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/projects/<slug>/cover.jpg - Paste that URL into the
coverfield of the project's MDX file (or thephotofield 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:
- Full Supabase URL — paste as-is in MDX frontmatter
cover:orgallery: /images/...path — looks underpublic/images/locally (legacy)- 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
| Surface | Aspect ratio | Min size |
|---|---|---|
| Project cover | 16:10 (landscape) | 1600×1000 |
| Project gallery | flexible | 1600 px long side |
| Team portrait | 4:5 (portrait) | 1200×1500 |
| Still | flexible | 1600 px long side |
| Kit hero | 16: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:
- Offline dev breaks images. No connection to
pstvpdcqmsodirmrvzam.supabase.comeans every image is a broken image icon. The DB seeds fine; the pages render; just the<img>tags fail. - 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 resetagain.
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.