Architecture, trust model, and Luckee OSS conventions for tracking Vercel Web Analytics across many projects.
My Many Domains is open-source software for developers who deploy multiple sites on Vercel and want pageviews and visitors from Vercel Web Analytics in one dashboard, without giving a hosted SaaS your Vercel API token. The public website handles marketing and Luckee sign-in; website lists and analytics calls run on your machine through a small Express companion API.
A free Luckee account unlocks GitHub clone URLs on the marketing site and syncs profile metadata for sign-in. Analytics itself is not behind a paid plan.
For coding agents
Reference: Web ADR 022 — Local-first cloud surface
| Capability | In the UI | Backend |
|---|---|---|
| Multi-site overview chart | /dashboard (local surface) | GET /api/websites/analytics/overview |
| Per-site analytics chart | /website-detail-page | GET /api/websites/:websiteId/analytics |
| Tracked websites list | /projects | GET /api/websites |
| Add / import from Vercel | Dashboard add form | POST /api/websites, GET /api/websites/discover |
| Cloud onboarding + health probe | /app (cloud surface) | GET /api/health |
| Marketing + auth | /, /login, /signup | Supabase + POST /api/account/sync |
Website fields: hostname, display name, Vercel project and team ids, L7/L30 pageview rollups, and analyticsFetchedAt on each website row.
Two different “Vercel Analytics”
Your projects: Vercel Web Analytics API via the local companion and your VERCEL_ACCESS_TOKEN.
This marketing site: optional @vercel/analytics on the Vercel-hosted web app (mymanydomains.com traffic only). See ADR 039.
GitHub org: Luckee-Core
| Repository | Runs on | Owns | Does not own |
|---|---|---|---|
| my-vercel-analytics-web | Vercel (cloud) + local dev | UI, APP_SURFACE, browser auth, API clients | Postgres, Vercel token, website rows |
| my-vercel-analytics-express-server | Your machine (default port 3009) | Postgres schema, websites CRUD, Vercel API calls | Hosted marketing site, Luckee auth infrastructure |
Full env and route tables: my-vercel-analytics-express-server/docs/oss/wire-contract.md in a full clone.
ADRs: Web 022 (local-first surface), Express 011 (local companion only).
Three zones: browser, local companion, and Luckee-operated cloud services. Your Vercel token never belongs in the web app or hosted backends.
Only NEXT_PUBLIC_* variables are visible to client code. The browser may hold a Supabase anon JWT. On the local surface it calls the companion directly; on cloud /app it probes GET /api/health on localhost only — not your project analytics.
Holds VERCEL_ACCESS_TOKEN and DATABASE_URL. CORS uses WEB_ORIGIN; development allows localhost and 127.0.0.1. Exits on Railway unless ALLOW_COMPANION_IN_CLOUD=true. There is no auth on /api/websites/* in v1 — treat it as localhost-trusted.
Luckee-operated hosted services handle Supabase auth, optional account profile sync, and transactional email. They never store your Vercel token or website analytics rows.
| Secret / data | Allowed | Never |
|---|---|---|
VERCEL_ACCESS_TOKEN | Companion .env | Web app, Vercel web env, hosted backends |
Website rows | Local Postgres | Cloud Postgres (v1) |
Supabase service role | Hosted account API (server-side only) | Browser |
Email provider / auth hook secrets | Luckee hosted auth email pipeline | MMD web or companion |
Sign-up, resend, and magic-link email are not composed in the browser. Supabase Auth delivers messages through Luckee-hosted infrastructure. See web SECURITY.md for operator notes.
NEXT_PUBLIC_APP_SURFACE is cloud or local. Production Vercel sets cloud; local dev defaults to local when unset.
| Value | Typical deploy | Notes |
|---|---|---|
cloud | Vercel production (mymanydomains.com) | Set explicitly in production |
local | npm run dev on your machine | Default when NEXT_PUBLIC_APP_SURFACE is unset in development |
| Path | Cloud | Local |
|---|---|---|
/ | Marketing landing | Marketing or local home |
/login, /signup, /auth/callback | Auth | Auth (when configured) |
/app | Signed-in onboarding + companion probe | Not the primary entry |
/dashboard, /projects, /website-detail-page | Redirect to /app (LocalSurfaceRouteGuard) | Full dashboard |
/docs/api | Works if companion reachable from browser | Primary API reference UI |
/learn | Static documentation | Same |
Cloud /app runs probeLocalCompanionThunk: checking → ready or unavailable. Ready shows a banner to run the web app locally with NEXT_PUBLIC_APP_SURFACE=local — it does not embed the full dashboard on the hosted origin.
| Variable | Surface | Purpose |
|---|---|---|
NEXT_PUBLIC_APP_SURFACE | both | cloud or local |
NEXT_PUBLIC_LOCAL_API_URL | both | Companion base URL (default http://127.0.0.1:3009) |
NEXT_PUBLIC_SUPABASE_URL | cloud | Auth |
NEXT_PUBLIC_SUPABASE_ANON_KEY | cloud | Auth |
NEXT_PUBLIC_CMS_URL | cloud | Account API base |
NEXT_PUBLIC_SITE_URL | cloud | Email redirect origin |
From Vercel token in the companion .env to charts in the local dashboard — no analytics upload through the hosted origin.
Vercel project setup: Vercel Analytics docs
Does the hosted site call Vercel for my projects?
No. Only your local companion uses your Vercel token. The cloud site checks that the companion is running via a localhost health probe.
Where are website rows stored?
In your local Postgres on the companion. v1 does not sync websites to cloud Postgres.
Can I use the full dashboard on mymanydomains.com?
The full dashboard runs on your machine with APP_SURFACE=local. Production hosts marketing, auth, and setup.
Success: { success: true, data }. Error: { success: false, error: string, message? }. Health: GET /api/health → success + data.status.
| Method | Path | Purpose |
|---|---|---|
GET | /api/health | Liveness |
GET | /api-docs.json | API docs catalog for /docs/api |
GET | /api/websites | List tracked websites |
POST | /api/websites | Create website |
GET | /api/websites/discover | Vercel import candidates |
GET | /api/websites/analytics/overview | Multi-site chart (timeFrame, groupBy) |
GET | /api/websites/:websiteId/analytics | Per-site analytics |
Interactive catalog when the companion is running: /docs/api
| Method | Path | Purpose |
|---|---|---|
GET | /api/health | Liveness |
GET | /api/account | Account profile (JWT) |
POST | /api/account/sync | Upsert user from JWT |
| Variable | Scope | Purpose |
|---|---|---|
PORT | companion | Listen port (default 3009) |
DATABASE_URL | companion | Local PostgreSQL |
VERCEL_ACCESS_TOKEN | companion | Vercel API (required for analytics) |
VERCEL_TEAM_ID | companion | Optional default team scope |
WEB_ORIGIN | companion | CORS allowlist |
Full env and route tables: my-vercel-analytics-express-server/docs/oss/wire-contract.md in a full clone.
Versions follow each repo's package.json at release time.
Luckee Next.js template conventions. Full ADRs: .cursor/architecture/ in the web repo.
src/app/**/page.tsx — thin routessrc/packages/<feature>/ — feature UI (dashboard, landing, learn, …)src/store/thunks — async side effects onlysrc/api/local — companion clientsrc/api/account — cloud account clientsrc/model — Website and analytics DTO typessrc/config — APP_SURFACE, routes, Supabase| ADR | Summary | MMD |
|---|---|---|
| 001 | Redux: flat dumps/current/builders; manual thunks; no selector functions | Yes |
| 002 | Thin app routes; features in src/packages/ | Yes |
| 003 | Styles object + Tailwind template literals | Yes |
| 004 | src/api clients; thunks call APIs | Yes — local vs account split |
| 008 | Static /website-detail-page + currentWebsite | Yes |
| 011 | src/model entity types | Yes |
| 012 | Form inputs under packages; zero-prop modals | Yes — add website form |
| 014 | Stripe Checkout return | N/A |
| 022 | Local-first cloud surface | Yes — product core |
| 039 | Vercel Analytics component on hosted web | Yes — marketing only |
Express server template: thin routers, handlers, and processX() business logic. No src/domains/.
src/services/websites/ — router, handlers, processXsrc/data/websites/ — CRUD onlysrc/model/website.ts — row typessrc/services/managed/vercel-config.ts — token from env at startupsql/ — schema migrationsdocs/oss/wire-contract.md — pair contract| ADR | Summary | MMD |
|---|---|---|
| 001–002 | Services + router factory; handlers → processX() | Yes |
| 003 | src/data/{table}/ CRUD only | Yes — websites |
| 004 | Managed clients at startup | Yes — Vercel config |
| 006 | Emoji logging; success/error JSON | Yes |
| 010 | No SQL functions; updated_at in CRUD | Yes |
| 009 | Stripe Checkout | N/A |
| 011 | Local companion only | Yes — product core |
My Many Domains is one slice in a family of Next.js + Express repos that share ADR numbering and agent rules. Product-specific decisions (local companion, account-only cloud API) sit on top of the same templates.
Stripe Checkout ADRs (web 014, express 009) are template placeholders — not used in My Many Domains.
| Term | Meaning |
|---|---|
| Companion | my-vercel-analytics-express-server on your machine; owns Postgres and Vercel API calls. |
| APP_SURFACE | NEXT_PUBLIC_APP_SURFACE: cloud (hosted marketing/auth) or local (full dashboard). |
| Probe | Browser GET /api/health against localhost from /app; does not upload analytics. |
| Web Analytics (product) | Vercel Web Analytics API data for your projects via the companion. |
| Web Analytics (marketing) | @vercel/analytics on the mymanydomains.com deployment (ADR 039). |
| Account sync | POST /api/account/sync on NEXT_PUBLIC_CMS_URL with Supabase JWT — profile only, not analytics. |
| Luckee | Shared auth and OSS template family; free sign-in for clone URLs and profile sync. |
Agents: after this page, open .cursor/rules/AGENTS.md in the repo you are editing.