Project One

Public Funnel Pages

The app serves each tenant's generated funnel pages from its own origin (docs/12 "own-domain step 1"): the lead page and its thank-you page, raw HTML straight from the pages table. Custom domains later become a CNAME + host→instance mapping in front of the same handler.

Endpoint Auth Rate limit
GET /p/{instanceId} public 120/min per IP — shared bucket with /thanks
GET /p/{instanceId}/thanks public shared bucket above

GET /p/{instanceId} · GET /p/{instanceId}/thanks

Serves the newest pages row for the instance (type = 'lead' or 'thank_you', status IN ('draft','published'), newest created_at wins).

Responses

Status Body
200 raw HTML, content-type: text/html; charset=utf-8. Drafts additionally carry x-robots-tag: noindex (published rows carry no robots header)
404 This page is still being put together. (text/plain) — deliberately identical for unknown instance, page not generated, empty html, or keyless demo (anti-enumeration)
503 This page is having a moment — please refresh shortly. (text/plain, retry-after: 30, cache-control: no-store) — the live query failed. Split out from the 404 (#177): a page served to paid traffic must not report GONE during an outage, because that can get the ad disabled and the URL de-indexed while spend continues. An outage is temporary and retryable; absence is not
404 Not found when the path segment is empty
429 shared JSON envelope

Semantics worth knowing

  • Newest-wins across statuses: a draft created after publish supersedes the published page (served with noindex). Draft pages are URL-viewable by anyone — only search engines are excluded.
  • Lead and thank-you select independently — a funnel can have a live lead page whose /thanks still 404s.
  • The served HTML posts to relative /api/leads and /api/bookings paths, which resolve against this same origin — same-origin by construction, no CORS needed. At publish time an absolute base (PUBLIC_BASE_URL) can be baked in instead.
  • No caching yet: every request hits the DB. CDN/ISR caching for these pages is tracked as issue #104 — revisit this page when that lands.

Source: app/p/[instanceId]/route.ts, app/p/[instanceId]/thanks/route.ts, src/lib/live/serve-page.ts