# 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 two `NEXT_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) 1. Create a project at (free tier). 2. Open **SQL Editor → New query**, paste all of [`docs/supabase-setup.sql`](./supabase-setup.sql), and **Run**. This creates the metadata tables, the demo RLS policies, **and the public `media` storage bucket** (the bucket is what makes uploaded/edited/captured files survive a reload — without it the app silently falls back to non-durable URLs). 3. Confirm **Storage** now lists a bucket named `media` marked **Public**. 4. **Project Settings → API** → copy: - **Project URL** → `NEXT_PUBLIC_SUPABASE_URL` - **anon public** key → `NEXT_PUBLIC_SUPABASE_ANON_KEY` (Never use the `service_role` key in the client — it stays server-side only, if used at all.) > 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 1. → **Add New → Project** → import your GitHub repo. 2. **Configure the project:** | Setting | Value | |---|---| | **Root Directory** | `apps/web` | | **Framework 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/sdk` is consumed from TypeScript source > via Next's `transpilePackages`, so `pnpm install` (which links the workspace) is enough. 3. **Environment Variables** (Project → Settings → Environment Variables), for Production + Preview: **Client (safe to expose — required for persistence):** | Name | Value | |---|---| | `NEXT_PUBLIC_SUPABASE_URL` | your Project URL | | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | your anon public key | | `NEXT_PUBLIC_SUPABASE_BUCKET` | `media` *(optional; default is `media`)* | **Server-only (optional — do NOT prefix with `NEXT_PUBLIC`):** | Name | Value | |---|---| | `GEMINI_API_KEY` | a Gemini key *(only for the AI generative-edit button)* | | `GEMINI_IMAGE_MODEL` | `gemini-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_KEY` the generative-edit route returns 503 while all the free in-browser AI keeps working. Optional theming/feature flags (`NEXT_PUBLIC_APG_*`) are documented in [`docs/ENV.md`](./ENV.md). 4. **Deploy.** ## 4. Verify after the first deploy - Response headers include a per-request `content-security-policy` with a `nonce-…` (confirms the nonce CSP middleware works on Vercel — expected; the app renders dynamically, not as a static export). - Import a photo → its `src` becomes a `https://.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).