Deployment — Vercel + Render
How the Meta Museum system maps onto hosted infrastructure.
| Piece | Host | Config |
|---|---|---|
| Next.js 16 web app (29 pages, 125 API routes) | Vercel | `vercel.json` |
| Postgres (system of record) | Neon or Vercel Postgres | `DATABASE_URL` |
| Validation service (pySHACL) | Render | `render.yaml` |
| Reconciliation service (+ Redis) | Render | `render.yaml` |
| AG2 worker (optional, disabled by default) | Render | `render.yaml` |
| Background workers (outbox / publish queue) | TBD — see Part C(#part-c--background-workers) | — |
| Solr / GraphDB (Era C search + graph) | Off for this deploy | gated by `OUTBOX_PROJECT_TO_*` |
A read-only public demo needs only Vercel + Neon. The Render services add validation/reconciliation; the workers add async projection/publishing.
---
Part A — Next.js on Vercel
One-time setup
- Import the repo in Vercel. Framework auto-detects as Next.js.
- `vercel.json` already overrides the build:
- Provision Neon Postgres (or Vercel Postgres) and copy the pooled connection string.
- Set the env vars below (Project → Settings → Environment Variables), then deploy.
- `buildCommand: next build` — critical. The repo's `pnpm build` runs `session:closeout:check` first, which fails 24h after the last close-out and would break every deploy. `next build` skips that guard. Local `pnpm build` is unchanged and still enforces the guard.
Vercel environment variables
| Var | Required | Value |
|---|---|---|
| `DATABASE_URL` | ✅ | Neon pooled connection string |
| `METAMUSEUM_STORAGE_MODE` | ✅ | `postgres` |
| `AUTH_SECRET` | ✅ | `openssl rand -base64 32` |
| `BASE_URL` | ✅ | `https://<your-app>.vercel.app` |
| `METAMUSEUM_PUBLIC_READ_BASE_URL` | ✅ | same as `BASE_URL` |
| `AUTH_GITHUB_ID` / `AUTH_GITHUB_SECRET` | ⛔ optional | enables GitHub sign-in; omit for public read-only |
| `VALIDATION_SERVICE_URL` | ⛔ optional | `https://metamuseum-validation.onrender.com/validate` (Part B) |
| `METAMUSEUM_AG2_BRIDGE_ENABLED` | ⛔ optional | leave unset (bridge stays off) |
Notes:
- Storage self-heals on a fresh DB. Core stores (`records.ts`) catch a missing Postgres document and seed an empty `[]`, so an empty Neon DB won't crash the app.
- Runtime is Node, not Edge for API routes (required by `pg` + Auth.js v5) — this is the App Router default; no action needed.
- Read-only FS caveat: the 16 managed documents go to Postgres, but a route that writes a non-managed JSON file would hit Vercel's read-only filesystem. Core browse/explore/records flows are managed-doc only.
---
Part B — Python services on Render
`render.yaml` is a Blueprint defining three FastAPI web services + a Redis cache.
Setup
- In Render: New → Blueprint, point at this repo. It reads `render.yaml`.
- Each service: own `rootDir`, `pip install -r requirements.txt`, and
`uvicorn main:APP --host 0.0.0.0 --port $PORT` (bypasses the hardcoded
`127.0.0.1` in each `__main__`). Health check: `/health`.
- Redis (`metamuseum-reconcile-cache`) is wired into the reconciliation
service via `RECONCILIATION_REDIS_URL` automatically.
- AG2 worker is optional — delete that block from `render.yaml` unless you
plan to enable the bridge.
Wire services back to Vercel
After Render assigns URLs, set on Vercel:
- `VALIDATION_SERVICE_URL = https://metamuseum-validation.onrender.com/validate`
The reconciliation service is invoked by operator scripts/ops tooling rather
than the Next runtime; point those at `https://metamuseum-reconciliation.onrender.com`.
Free-tier caveat: Render free web services sleep after inactivity and
cold-start in ~30–60s. Fine for a demo; upgrade to paid for pilots.
---
Part C — Background workers
Two long-running drains exist as `tsx` scripts; neither runs on Vercel
(no persistent processes):
| Worker | Script | Needed when |
|---|---|---|
| Outbox projector | `pnpm outbox:projector` | Solr/GraphDB projection is enabled (off in this deploy) |
| Publish queue worker | `pnpm publish:queue:worker` | Wiki/publication publishing is used |
For the read-only demo: neither is required. Decide before enabling
projection or publishing. Options:
- Vercel Cron → drain-once endpoint (recommended for demo→beta).
Add `vercel.json` cron entries hitting thin API routes that call the
`--once` drain path, e.g. every 5 min. Serverless-native, no extra host.
Requires adding small `/api/cron/outbox` + `/api/cron/publish` routes.
- Render Cron Job / Background Worker (recommended if projection is heavy).
Add a Node-runtime cron service to `render.yaml` running
`pnpm outbox:projector:once`. Keeps long/heavy drains off the request path.
- External scheduler (GitHub Actions on a schedule) — simplest, lowest
throughput; good for low-volume publishing.
Recommendation: ship the demo with workers off; when projection/publishing
is turned on, use Vercel Cron (option 1) first and graduate to **Render
(option 2)** only if drain volume outgrows serverless timeouts.
---
Quick path to a live demo
- Neon DB → copy pooled `DATABASE_URL`.
- Vercel import → set `DATABASE_URL`, `METAMUSEUM_STORAGE_MODE=postgres`,
`AUTH_SECRET`, `BASE_URL`, `METAMUSEUM_PUBLIC_READ_BASE_URL` → deploy.
- (Optional) Render Blueprint → set `VALIDATION_SERVICE_URL` on Vercel → redeploy.
- Workers stay off until projection/publishing is needed.