5.5 KiB
Deploying for free (Vercel + Supabase free tier)
This app runs 100% on free tiers: Vercel Hobby (Next.js hosting) + Supabase Free (Postgres for metadata + Storage for the media files). No paid services are required. The in-browser AI (object detection, faces, OCR) runs on the user's device for free; only the optional Gemini generative edit needs a key (and can be left off).
TL;DR — run the SQL once, push to GitHub, import into Vercel with Root Directory =
apps/web, set the twoNEXT_PUBLIC_SUPABASE_*env vars, deploy. Everything (photos, videos, tags, versions, comments, albums, edited copies) then persists to your Supabase project.
1. Supabase (database + file storage)
- Create a project at https://supabase.com (free tier).
- Open SQL Editor → New query, paste all of
docs/supabase-setup.sql, and Run. This creates the metadata tables, the demo RLS policies, and the publicmediastorage bucket (the bucket is what makes uploaded/edited/captured files survive a reload — without it the app silently falls back to non-durable URLs). - Confirm Storage now lists a bucket named
mediamarked Public. - Project Settings → API → copy:
- Project URL →
NEXT_PUBLIC_SUPABASE_URL - anon public key →
NEXT_PUBLIC_SUPABASE_ANON_KEY(Never use theservice_rolekey in the client — it stays server-side only, if used at all.)
- Project URL →
Free-tier note: a Supabase project pauses after ~7 days idle. Open the dashboard to unpause before a demo. Storage free tier is 1 GB, Postgres 500 MB — plenty for a demo library.
2. Push the repo to GitHub
Commit everything including pnpm-lock.yaml at the repo root (Vercel uses it to detect pnpm and
install the whole workspace). Do not commit .env.local (it's gitignored).
3. Vercel
-
https://vercel.com → Add New → Project → import your GitHub repo.
-
Configure the project:
Setting Value Root Directory apps/webFramework Preset Next.js (auto-detected) Install Command pnpm install(default)Build Command next build(default)Output .next(default)Node.js Version 20.x No separate SDK build step is needed:
@photo-gallery/sdkis consumed from TypeScript source via Next'stranspilePackages, sopnpm install(which links the workspace) is enough. -
Environment Variables (Project → Settings → Environment Variables), for Production + Preview:
Client (safe to expose — required for persistence):
Name Value NEXT_PUBLIC_SUPABASE_URLyour Project URL NEXT_PUBLIC_SUPABASE_ANON_KEYyour anon public key NEXT_PUBLIC_SUPABASE_BUCKETmedia(optional; default ismedia)Server-only (optional — do NOT prefix with
NEXT_PUBLIC):Name Value GEMINI_API_KEYa Gemini key (only for the AI generative-edit button) GEMINI_IMAGE_MODELgemini-2.5-flash-image(optional)Without the Supabase vars the app still runs, but on browser-local storage only (data won't sync across devices). Without
GEMINI_API_KEYthe generative-edit route returns 503 while all the free in-browser AI keeps working.Optional theming/feature flags (
NEXT_PUBLIC_APG_*) are documented indocs/ENV.md. -
Deploy.
4. Verify after the first deploy
- Response headers include a per-request
content-security-policywith anonce-…(confirms the nonce CSP middleware works on Vercel — expected; the app renders dynamically, not as a static export). - Import a photo → its
srcbecomes ahttps://<project>.supabase.co/...URL → hard-reload: it still shows. Same for a camera capture and an edited copy. - Edit a photo twice → the Info panel shows Version 3; reload → history + comments persist.
- The Objects sidebar section fills in as on-device detection tags photos.
What persists to the database
Everything is stored as JSONB per item (gallery_media.data) + binaries in Storage, so a reload
restores it all: tags, detected objects/labels, faces, versions[] (full edit history + audit log),
comments[], albums, favorites, edits, and Save-as-Copy items (each copy is a new row with its own
2-entry history). System/smart albums (incl. the auto object albums) are regenerated on load, so
they're intentionally not stored.
Gotchas / limits (free tier)
- Gemini AI edit + Vercel body limit: Vercel Hobby caps request bodies at ~4.5 MB. The AI-edit route is capped accordingly and large images are downscaled client-side first; very large images may still be rejected. The free in-browser AI is unaffected.
- Single-user demo: the Supabase adapter does a full-state sync (no auth). For multi-user, add
Supabase Auth and scope rows per user before going to production (the RLS policies are open for the
demo — tighten them with
auth.uid()). - In-browser video export (trim/overlays/watermark/etc.) requires the source video to be readable for canvas capture. Same-origin (Supabase Storage with CORS, which is on by default) works; a cross-origin host without CORS headers can't be exported in-browser (the editor now surfaces a clear error instead of failing silently).
- Heavy AI libs (TensorFlow.js / face-api / tesseract / transformers / imgly) are dynamically imported on the client only — they don't bloat the server bundle, but first analysis downloads model weights (cached afterwards).