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
/thanksstill 404s. - The served HTML posts to relative
/api/leadsand/api/bookingspaths, 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