A Health Landing Page Is a Lead Form With Rules Attached
The sections are the easy part. Three constraints a SaaS page skips: no clinical detail in the form, no person in the analytics, and a CSP that makes every tracker a commit.
A health landing page needs the same parts as any other — a clear offer, proof, one primary action — plus three constraints a SaaS page does not carry: the form must not collect clinical detail, the analytics must contain no person, and every third-party script is a data disclosure you have to be able to justify. Get those three wrong and a beautiful page is a liability.
This is written from the engineering side of a storefront that runs a cookieless analytics setup with five whitelisted events, a Content-Security-Policy that blocks embeds by default, and lead forms that collect exactly one field. None of it is legal advice — your jurisdiction and your relationship to a covered entity decide what applies. All of it is the technical shape that makes the legal conversation short.
The sections are the easy part
| Section | Generic SaaS landing page | Health / clinic landing page |
|---|---|---|
| Hero CTA | "Start free trial" | "Book an appointment" / "Check availability" — a time, not a trial |
| Core proof | Customer logos | Named practitioners with credentials, registration numbers, and photos |
| Secondary proof | G2 badges, star ratings | Accreditations, insurers accepted, years in practice |
| Primary form | Email + company + team size | Name + contact method. Nothing about why they are calling |
| Objection handling | Pricing table | Cost, insurance cover, what the first visit involves, cancellation policy |
| Location | Footnote | A section — address, parking, transit, accessibility, opening hours |
| Structured data | SoftwareApplication | MedicalBusiness / Physician, with address and openingHours |
| Chat widget | Default yes | Default no, until someone has read what it stores and where |
The last two rows are where health pages diverge most from the template they were built on. Location is a conversion section, not a courtesy: a person choosing a clinic is choosing a journey across town. And the chat widget, dropped in by habit, is the single most common way an otherwise careful page starts collecting symptom descriptions into a third party's database.
The closest shape in this catalog is ASoc Cover, an insurance landing template whose four cover types include health. It is worth looking at for the funnel rather than the vertical: a two-minute-quote hero, a review trust band, a transparent-pricing section, and a five-question FAQ that ends in one action. That is the structure a clinic page wants. What changes is what the form is allowed to ask.
The lead form: one field, and the ones you must refuse
The waitlist form on this site is a Server Action and it collects a single field. The full validation path, copied from src/lib/actions/newsletter.ts:
"use server";
import { EMAIL_RE } from "@/lib/validation";
export async function subscribeToWaitlist(_prev: FormState, formData: FormData) {
// Honeypot: real users never fill this hidden field.
if (formData.get("company")) return { ok: true, message: "You're on the list!" };
const email = String(formData.get("email") ?? "").trim().toLowerCase();
if (!EMAIL_RE.test(email)) {
return { ok: false, message: "Please enter a valid email address." };
}
// …deliver, then return a message. No logging of the address itself.
}
Three things in nine lines are worth copying onto a health page.
The honeypot returns success. A bot that filled the hidden company field gets the same cheerful message a human gets, and nothing is stored. Failing loudly tells the bot what to change.
Validation is a regex, not a library. A two-field form does not need client-side cross-field logic, so it does not need a validation package in the bundle. The whole check is server-side, which is also the only place it counts.
Nothing is logged on the happy path. The error branches log a status code and a message; the success branch logs nothing. On a health form that distinction is the whole game — a logging line that includes formData is how a free-text field ends up in a log aggregator, a crash reporter, and an analytics replay tool in one step.
Then the refusals. A marketing lead form should not have a field for why the person is getting in touch. "Tell us about your symptoms", "What are you being treated for", a condition dropdown, a date of birth, an insurance member number — each one converts a marketing contact into a health record, and does it before the person has any relationship with you. The clinical conversation belongs behind a login or on the phone. A landing page's job is to get someone to the point where that conversation can happen safely.
The practical rule: every field on the form must be justifiable as "needed to contact this person back". Name and one contact method pass. A free-text box labelled "How can we help?" is the ambiguous one — people will put clinical detail in it whether you asked for it or not, so if you keep it, treat whatever arrives in it as sensitive from the moment it lands.
Analytics with no person in it
This site runs cookieless Vercel Web Analytics and sends five conversion events, defined as a closed TypeScript union in src/lib/analytics.ts:
type ConversionEvent =
| "preview_opened"
| "buy_clicked"
| "waitlist_joined"
| "blog_cta_clicked"
| "wishlist_added";
export function track(event: ConversionEvent, props?: Record<string, string | number>) {
try {
vercelTrack(event, props);
} catch {
// Analytics must never break UX — swallow any client-side error.
}
}
The union is the control. A typo cannot invent a sixth event, and because every call site has to name one of five constants, there is one short list to audit when someone asks what this page sends. The props are catalog slugs and tier names — no email, no name, no user id, by a rule stated in the file's own comment.
A health page should be at least this strict, and the reason is specific rather than abstract: on a clinic site the URL itself can be the sensitive value. A page view on /treatments/fertility attached to a stable visitor id is a health inference about a person, even though no form was filled. That is why cookieless matters more here than on a storefront — no cookie means no durable identifier to attach the path to — and why "we only send page views" is not automatically safe.
Two things follow:
- Keep sensitive routes out of event props. If you fire a custom event on a treatment page, send a coarse category, not the exact slug.
- Turn off session replay, or confine it to pages with no form. A replay tool records keystrokes; a keystroke in a free-text box on a clinic page is the exact thing you decided not to store.
The CSP is the list of third parties you have agreed to
Every script you add to a health page is a party that can see the URL, the referrer, and anything the script chooses to read from the DOM. The useful discipline is to make that list explicit and small. This project's policy, in next.config.ts, names every external origin and why:
const contentSecurityPolicy = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' https://app.lemonsqueezy.com https://va.vercel-scripts.com`,
"style-src 'self' 'unsafe-inline'",
`connect-src 'self' ${supabaseOrigin}`,
"frame-src 'self' https://*.lemonsqueezy.com https://*.vercel.app",
"frame-ancestors 'none'",
"form-action 'self' https://*.lemonsqueezy.com",
"object-src 'none'",
].join("; ");
The file carries a one-line rule above it — each origin verified against the code that loads it; do not add speculative hosts — and that rule is what makes the policy useful rather than decorative. Dropping a marketing pixel onto a page covered by this header fails twice: the script will not load (script-src), and if it did, its beacon would not send (connect-src). Both failures are visible in the browser console, which means adding a tracker becomes a deliberate act with a commit attached to it, instead of something that happens in a tag manager on a Friday.
form-action is the quiet one. It restricts where a form on your page is allowed to POST — which is the difference between an injected form exfiltrating a submission and an injected form doing nothing at all. On a page that collects patient contact details, that directive earns its line.
frame-ancestors 'none' is the other. It stops the page being embedded in someone else's site, which is how a clickjacked "Book now" button gets built.
Mistakes and how they show up
| Mistake | Symptom | Fix |
|---|---|---|
| Free-text "how can we help" on the marketing form | Symptom descriptions in your inbox, CRM, and log aggregator | Remove it, or treat the whole pipeline as handling sensitive data |
| Chat widget added by default | A third party holds transcripts you have never read | Remove until someone has reviewed retention and location; it is not a launch requirement |
| Session replay on a page with a form | Keystrokes recorded in free-text fields | Disable, or exclude every page with an input |
| Treatment slug sent as an event prop | Condition inferred per visitor from your own analytics | Send a coarse category; keep the path out of props |
| Pixels managed in a tag manager | Nobody can answer "what does this page send?" | Put third-party origins in the CSP so additions need a commit |
| Trust section is stock photos | Reads as a template; no credential a visitor can verify | Named practitioners, registrations, accreditations, insurers accepted |
| Location buried in the footer | Visitors bounce to a competitor they can find | A location section with address, hours, transit and accessibility |
SoftwareApplication schema inherited from the template | Wrong entity type for rich results | MedicalBusiness or Physician, with address and openingHours |
| No cancellation or cost information | "Book now" clicked, then abandoned at the first unknown | Answer cost, cover and first-visit expectations in the FAQ |
Frequently asked questions
Is a health landing page subject to HIPAA? It depends on who you are and what you collect, and it is a question for counsel rather than a blog post. What is unambiguous is the engineering direction it points: the less a marketing page collects, stores and forwards, the smaller that question gets. A page with one contact field, no clinical inputs, no replay and a short third-party list is the easy version of the conversation.
Can I run analytics on a healthcare site at all? Generally yes, and cookieless, aggregate analytics is the conservative starting point. The thing to watch is not the pageview count but the identifiers and the URLs: a durable visitor id plus a condition-specific path is an inference about a person, regardless of which tool produced it.
Do I need a separate page per treatment? Only if each one has its own audience and its own question to answer. A treatment page that exists to repeat the homepage with one word changed will not rank, and on a health site it also multiplies the number of sensitive URLs you now have to think about in analytics.
What is the single highest-impact section to add? Named practitioners with verifiable credentials. It is the proof a visitor actually evaluates before booking, it is the hardest thing for a competitor to copy, and it is the part generic landing-page templates leave as a stock-photo placeholder.
Templates in this post
ASoc Cover is the closest funnel in the catalog: an insurance marketing site whose four cover types include health, built around a two-minute-quote hero, a review trust band, testimonials and a five-question FAQ that ends in one action. ASoc Echo and ASoc Edge are the SaaS-shaped counterexample this post argues with — a chatbot landing page and an applied-AI agency site, both "book a demo" pages with the trust, FAQ and team sections a clinic page needs and the form fields it does not.
All three are static Next.js builds, which is what lets a health page ship with no third-party script at all. Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
