Practice Website Kit — Astro + PagesCMS + Cloudflare Pages
The stack, and why
| Layer | Choice | Why |
|---|---|---|
| Site framework | Astro | Ships static HTML with near-zero JavaScript by default — fast on phones, nothing to patch, content in Markdown with typed frontmatter. |
| Content editing | PagesCMS (app.pagescms.org) | Free, open-source, Git-based: staff edit in web forms; every save is a Git commit. No database, no CMS server to maintain. |
| Hosting | Cloudflare Pages | Free tier fits a practice site comfortably (generous build minutes and bandwidth, many custom domains), no branding, commercial use allowed, global CDN, automatic HTTPS. Deploy = connect the Git repo. |
| Repo | GitHub (practice-owned) | The single home of the site: Astro reads it, PagesCMS writes to it, Cloudflare builds from it. |
The architecture rule that makes this work: the website is a publication, not an application. Pages, profiles, and posts are content. Bookings, payments, messaging, and directories already have systems — the site embeds and links them; it never reimplements them and never receives their data.
Instructions for the agent
- Deliverable = a working repo the practice owns + the three connections made (PagesCMS → repo, Cloudflare Pages → repo + domain) + one verified end-to-end edit through the CMS. If you lack account access, produce the complete repo and a click-by-click connection checklist for the owner.
- Run the setup interview first. Never invent practice facts — credentials, licenses, insurance panels, fees, languages, years in practice. Placeholders, flagged, until the owner supplies them.
- Verify every platform behavior from the live config, not from memory: the PagesCMS filename-template rule in
references/pagescms-config.mdcost a real site its first save — treat the CMS save test as the definition of "configured."
Setup interview (ask once, before building)
- Practice identity — practice name, domain (and where DNS lives — Cloudflare hosting is smoothest when DNS is at Cloudflare too), logo/colors if any, phone/email to display.
- Shape — solo or group; provider list (names, credentials, license states, photo availability) for profile pages.
- Pages — confirm the blueprint in
references/site-blueprint.md(add/remove: e.g. a dedicated page per service? a resources/blog section? a referral directory embed?). - Languages — one language, or a mirrored tree (e.g. English at
/, Chinese at/zh/)? Mirrored means human-quality translated pages with their own slugs — budget the translation; never machine-translate clinical-adjacent content and ship it unread. - Dynamic embeds — which existing systems appear on the site (booking page, contact form app, referral directory, secure-messages entry point) and their URLs. These become iframe embeds + fallback links.
- Editors — who updates content after launch (owner only? an admin? providers writing posts?) — this decides PagesCMS access and how much of the site is CMS-managed vs. code-managed.
- Existing assets — current site content to migrate, photos (and their rights), Google Business Profile and directory listings the site must stay consistent with.
Hard rules
- The practice owns every account — domain registrar, GitHub, Cloudflare. Set up under the practice's logins, with recovery options the practice controls.
- Content is data, not code. Anything an editor might change (provider bios, services, fees, FAQs, posts) lives in Markdown/frontmatter behind a content schema — never hard-coded into
.astrotemplates. - PagesCMS filename templates use
{fields.slug}(or the collection's real field path). A bare{slug}is invalid and makes every CMS save fail with an opaque error. After fixing config, reload the CMS — it caches config per session. - The site collects nothing clinical. No symptom text boxes, no "tell us what's going on" forms. Contact = booking link/embed, phone, and a routed form app if needed — plus the crisis line (988 in the US, and 911 for emergencies) on the contact page and any page that invites contact, in the site's languages.
- Embeds get fallback links. Every iframe (booking, directory) is paired with a plain link to the same system, so a blocked frame never dead-ends a client.
- Claims discipline: panels, fees, credentials, and availability on the site must match the practice's source of truth (its roster/records). The website is the last place a fact should be edited — if a number lives in two places, name which one wins.
- Performance is a feature on a therapy site — people arrive anxious, on phones, from a directory profile. Compress images before they enter the repo; a 2 MB hero photo is a bug, not an asset.
Build order
- Repo + Astro scaffold + content model per
references/astro-setup.md, pages perreferences/site-blueprint.md. - Local build green; content placeholders flagged and listed for the owner.
- PagesCMS config + the real save test per
references/pagescms-config.md. - Cloudflare Pages connection + custom domain per
references/cloudflare-pages-deploy.md. - SEO/performance pass per
references/seo-and-performance.md. - Handoff per
references/maintenance-and-handoff.md; verify perreferences/test-checklist.md.
Reference files
references/architecture-and-stack.md— the three layers, data flow, what static means for a practicereferences/site-blueprint.md— the pages, what each must contain, therapy-specific mustsreferences/astro-setup.md— project layout, content collections + schema, multilingual treereferences/pagescms-config.md—.pages.yml, collections, media, the filename-template rulereferences/cloudflare-pages-deploy.md— project setup, build settings, custom domain, previewsreferences/seo-and-performance.md— metadata, structured data, sitemap/RSS, llms.txt, imagesreferences/maintenance-and-handoff.md— editing workflow, updates, backups, who owns whatreferences/test-checklist.md— acceptance tests, the CMS save test first