Macquarie Park Medical Centre
Medical practice website with Sanity CMS, doctor profiles, and online booking integration.
Headless CMS
Sanity CMS with visual editing, webhook-triggered rebuilds, and 12 content schemas
Online Booking
HotDoc integration across every page — header CTA, floating button, and dedicated booking page
Local SEO
JSON-LD MedicalOrganization schema, suburb targeting, Google Search Console connected
Accessibility
prefers-reduced-motion support, ARIA labels, focus-visible states, skip-to-content navigation
The Challenge
Macquarie Park Medical Centre is a bulk-billing GP practice serving one of Sydney’s most linguistically diverse suburbs. Their team of doctors collectively speaks Mandarin, Hindi, Nepali, Arabic, Sinhalese, and several other languages — reflecting the community they serve around Macquarie University and the surrounding business parks.
When we first looked at the practice’s web presence, it was a single-page placeholder: address, phone number, opening hours. Nothing about the doctors, nothing about the services, no way to book online. For a suburb where competing practices had full websites with HotDoc integration, this wasn’t just a branding problem — it was leaving bookings on the table. A parent searching “bulk billing GP Macquarie Park” at 10pm would find competitors before finding MPMC.
The practice director’s brief was practical:
- Staff need to update content themselves — doctors join, services expand, hours change
- Booking needs to be everywhere, not buried in one link
- Doctor profiles need to build trust before the first visit
- The site needs to appear in local search for surrounding suburbs
- It needs to work on mobile, where most patients will find it
Why We Chose This Architecture
Astro + Sanity CMS Over WordPress or Next.js
This decision came down to three factors specific to medical practice websites:
| Factor | WordPress | Next.js | Astro + Sanity |
|---|---|---|---|
| Content editing | Familiar but fragile (plugin conflicts, updates) | Requires developer for CMS integration | Sanity Studio is independent — hosted, versioned, zero maintenance |
| Performance | PHP rendering, plugin overhead, requires caching layer | Fast but requires Node.js server or ISR complexity | Static HTML at the edge — no server, no cold starts |
| Long-term cost | Hosting + security updates + plugin licences | Vercel costs scale with traffic | Cloudflare Pages free tier handles medical practice traffic |
| Content modelling | ACF works but schema changes require plugin updates | Headless CMS needed anyway | Sanity schemas defined in code, version-controlled, type-safe |
WordPress was considered because most medical practices use it. We chose against it for a specific reason: WordPress plugins are a maintenance liability. A medical practice shouldn’t need to worry about updating plugins to avoid security vulnerabilities. Astro generates static HTML — there’s no server to patch, no database to secure, no PHP runtime to keep current.
Next.js was considered for its React ecosystem, but the practice doesn’t need client-side interactivity beyond booking embeds. Shipping a React runtime for what is fundamentally a content site would degrade mobile performance without adding value.
Hand-Written CSS Over Tailwind
This is unconventional. Most Astro projects use Tailwind, and we use it on other sites. For MPMC, we wrote 2,400+ lines of custom CSS with a design token system. The reasoning:
The Obsidian design variant uses multi-layer parallax, SVG wave dividers, logo watermarks, and scroll-aware header transitions that would require significant Tailwind @apply workarounds or inline style overrides. At that point, Tailwind becomes overhead rather than utility. A custom CSS system with well-named custom properties (--color-teal, --color-primary-dark, --wave-height-mobile) gave us direct control over every animation, transition, and responsive behaviour.
The trade-off is clear: slower initial development, but every CSS rule serves a specific purpose and no utility classes ship unused.
Design Direction: Obsidian
Medical websites cluster around two aesthetics: sterile-clinical (white, stock photos, blue accents) and overly-casual (bright colours, cartoonish icons). Both fail. The first feels cold, the second undermines trust.
The Obsidian variant sits between them — dark navy (#0a1f38) and teal (#278378) that feels sophisticated without being corporate, paired with warm cream (#F5EDE3) sections that break up the darkness and keep the site approachable. The palette was tested specifically for medical contexts: does this feel trustworthy? Would a parent feel comfortable finding this practice through this site?
Parallax and Motion
Hero images, CTA bands, and section backgrounds use multi-layer parallax with three implementation details that matter:
- requestAnimationFrame throttling — scroll handlers fire at display refresh rate, not at every scroll event. This prevents the janky parallax that plagues sites using raw
scrollevent listeners. - Touch device detection — parallax is disabled entirely on mobile. The effect requires a mouse-scroll metaphor that doesn’t translate to touch scrolling, and the compositor overhead isn’t worth it on mobile GPUs.
- prefers-reduced-motion — if the OS-level setting is enabled, all transforms are killed but opacity fades are preserved. The user still gets entrance animations, just without movement that could trigger vestibular issues.
Typography Decision
DM Sans as a single self-hosted variable font, subset to WOFF2, served from the same origin. This eliminated the Google Fonts waterfall — a problem we measured at 180-300ms on 4G connections during development. One font file, one HTTP request, zero FOUT (flash of unstyled text) because the variable weight range covers headings through body text.
The alternative was loading Source Sans + a display font, which would mean two HTTP requests, two font files, and a layout shift when the display font loaded. For a medical site where mobile is the primary device, that 200ms matters.
Content Architecture
12 Sanity Schemas
The CMS isn’t a generic page builder — it’s a structured content model designed specifically for medical practices. Each schema enforces the right fields in the right format:
| Schema | Fields | Why It’s Structured |
|---|---|---|
| doctor | Name, photo (hotspot crop), credentials, role, slug, short bio, full bio (Portable Text), qualifications[], languages[], special interests[], availability status, display order | A doctor profile isn’t free text. Qualifications must be an array (rendered as badges). Languages must be tags (searchable). Status (available/joining-soon/on-leave) drives display logic. |
| service | Title, slug, icon, images[], short description, long description, what to expect, featured flag, display order | Services expand as practices grow. Structured fields mean every new service page is consistent without design work. |
| siteSettings | Logo, phone, hours, booking URL, social links, bulk billing badge, navigation | Singleton schema. One source of truth for clinic-wide data used across every page. |
| faqItem | Question, answer, category, order | FAQ entries referenced by multiple pages. Edit once, reflected everywhere. |
Every page template has CMS-first content with hardcoded fallbacks. The homepage hero tagline comes from Sanity’s homePage schema — but if that field is empty, a sensible default renders. This let us launch with polished content while the practice team learned the CMS at their own pace. No blank sections, no broken pages, no pressure.
The Fallback Strategy in Practice
This isn’t just a safety net — it’s a deliberate content migration pattern. During development, we populated every field with curated content. The practice team then reviewed each page in Sanity Studio and replaced our copy with their own language, one field at a time. Some fields they kept, some they rewrote. The site was never in a “half-finished” state because every field always had a value — either ours or theirs.
Booking Integration
Six Touchpoints, One Booking Platform
HotDoc is the dominant GP booking platform in Australia. Rather than building a custom booking system, we integrated HotDoc at every point in the patient journey where a booking decision might happen:
| Touchpoint | Implementation | Rationale |
|---|---|---|
| Sticky header CTA | Fixed “Book Online” button, all pages | Visible at every scroll position without hunting |
| Floating action button | Appears after 600px scroll; icon-only on mobile, text + icon on desktop | Catches patients who’ve scrolled past the header |
| Dedicated booking page | Full HotDoc widget embed at /book | For patients who navigate directly to book |
| Doctor profile CTAs | Direct booking link on each doctor’s page | ”I want to see THIS doctor” |
| Service page CTAs | Contextual booking on each service page | ”I need THIS service” |
| Welcome popup | Session-based modal (once per session, 1.5s delay) | First-visit prompt with bulk billing reminder |
The booking page presents three methods — online, phone, walk-in — side by side. This isn’t a design choice, it’s a patient access choice. Not every patient books online. Older patients or non-English speakers may prefer to call. Walk-in availability needs to be clear for patients already in the area.
Local SEO
JSON-LD MedicalOrganization Schema
Google’s structured data for medical practices is specific. We implemented the full MedicalOrganization schema with:
- Structured opening hours including Thursday late-night (8:30pm close)
- Geo coordinates for map positioning
areaServedlisting 8 suburbs explicitly: Macquarie Park, North Ryde, Ryde, Marsfield, Eastwood, Epping, Lane Cove, Chatswood- Accepted insurance indicators
- Google Search Console connected with URL prefix verification
After-Hours Guidance
The footer directs patients to Ryde and Chatswood Urgent Care Clinics and 000 for emergencies. This serves two purposes: genuine patient safety (someone on the site at 2am needs to know where to go) and a signal to Google that this is a legitimate, community-aware medical resource — not a content farm.
Performance
What We Measured
| Metric | Value | How |
|---|---|---|
| TTFB | Sub-second | Cloudflare edge delivery, static HTML, no origin server |
| Font waterfall eliminated | -180-300ms on 4G | Self-hosted DM Sans variable WOFF2, same-origin |
| Icon weight reduction | 822KB → ~15KB | Replaced Tabler Icons webfont with inline SVG sprite (14 medical icons) |
| Critical CSS | ~8KB inlined | Hero, buttons, layout in <head> — above-fold renders before stylesheet loads |
| Image strategy | Lazy loading + fetchpriority="high" on hero | Below-fold images don’t block initial paint |
The Icon Decision
The original design used Tabler Icons as a webfont — 822KB for an icon set where we used 14 icons. That’s 808KB of unused glyphs shipping to every visitor. We extracted the 14 medical service icons (General Health, Women’s Health, Men’s Health, etc.) as branded WebP images and the remaining UI icons as inline SVGs. Total cost dropped from 822KB to approximately 15KB.
Deployment
GitHub Actions with Sparse Checkout
The MPMC site lives inside a monorepo with 20+ other projects. A naive CI/CD pipeline would clone the entire repository on every build. We configured GitHub Actions with sparse checkout — the workflow only pulls the obsidian-astro/ subdirectory, keeping build times under 90 seconds and CI costs minimal.
The deployment pipeline: Sanity webhook → GitHub repository_dispatch event → sparse checkout → npm install && npm run build → Cloudflare Pages direct upload via wrangler. No manual steps, no developer involvement.
DNS and Domain Architecture
The domain setup required coordination across two providers:
- Cloudflare Pages hosts the site with a CNAME from
www.macquarieparkmedical.com.au - GoDaddy handles the apex domain (
macquarieparkmedical.com.au) with a 301 redirect towww— Cloudflare Pages doesn’t support bare apex domains without Cloudflare DNS - HSTS and TLS 1.3 enforced at the edge
- MX records verified intact after DNS changes — breaking email delivery during a website launch is an unacceptable failure mode for a medical practice
Technical Specifications
| Category | Detail |
|---|---|
| Framework | Astro 5.x (Static Site Generation) |
| CMS | Sanity CMS v5 (hosted studio, Stega visual editing) |
| Styling | Hand-written CSS, 2,400+ lines, custom property design tokens |
| Typography | DM Sans Variable (self-hosted, subset WOFF2, single file) |
| Hosting | Cloudflare Pages (free tier, edge-delivered) |
| CI/CD | GitHub Actions (sparse checkout, wrangler pages deploy) |
| Booking | HotDoc (iframe embed + external links, 6 touchpoints) |
| SEO | JSON-LD MedicalOrganization, sitemap, canonical URLs, Open Graph |
| Accessibility | prefers-reduced-motion, ARIA labels, focus-visible, skip-to-content |
| Schemas | 12 Sanity document types covering all editable content |
| Pages | 7 static + dynamic doctor and service routes |
| Service Categories | 14 medical specialties with custom branded icons |
| Delivery | Designed, built, and deployed to production in under 2 weeks |
Performance Benchmark: MPMC vs Surry Hills Doctors
We benchmarked MPMC against Surry Hills Doctors, a well-established Sydney GP practice running on Squarespace. Both sites serve similar patient demographics, offer HotDoc booking integration, and target overlapping local search terms. The comparison demonstrates what a purpose-built static architecture delivers versus a general-purpose website builder.
Lighthouse Scores (Desktop)
| Category | MPMC | Surry Hills Drs | Delta |
|---|---|---|---|
| Performance | 99 | 30 | +69 |
| Accessibility | 100 | 86 | +14 |
| Best Practices | 100 | 77 | +23 |
| SEO | 100 | 100 | — |
Core Web Vitals
| Metric | MPMC | Surry Hills Drs | What It Means |
|---|---|---|---|
| First Contentful Paint | 1.0s | 3.4s | How quickly the patient sees content |
| Largest Contentful Paint | 2.0s | 32.8s | When the main content finishes loading |
| Total Blocking Time | 60ms | 4,190ms | How long the page freezes during load |
| Cumulative Layout Shift | 0 | 0 | Visual stability while loading |
| Speed Index | 2.6s | 10.0s | How quickly the page appears visually complete |
| Time to Interactive | 2.0s | 33.2s | When the patient can actually use the page |
| Server Response (TTFB) | 70ms | 120ms | Initial server response time |
MPMC loads to full interactivity in 2 seconds. The reference site takes over 33 seconds — during which a patient on their phone at 10pm is staring at a half-loaded page or has already bounced to a competitor.
Page Weight
| Metric | MPMC | Surry Hills Drs | Ratio |
|---|---|---|---|
| Total payload | 185 KiB | 5,478 KiB | 29.6x smaller |
| Avg HTML per page | 52.8 KB | 456.7 KB | 8.6x smaller |
| Avg compressed transfer | ~11 KB | ~43 KB | 3.9x smaller |
| External script domains | 0 | 4 | Zero third-party |
| Inline scripts | 5 | 19 | 3.8x fewer |
| External scripts | 2 (self-hosted) | 36 | 18x fewer |
The 29.6x payload difference comes from Astro’s zero-JavaScript-by-default philosophy versus Squarespace’s 36 external scripts, jQuery runtime, and theme CSS. On a 4G mobile connection (the typical use case for GP search), MPMC’s 185 KiB loads in a single round trip. The reference site’s 5.4 MB requires multiple TCP windows to deliver.
Content Metrics
Coverage Comparison
| Metric | MPMC | Surry Hills Drs |
|---|---|---|
| Total pages | 21 | 34 |
| Total word count | 9,863 | 20,351 |
| Avg words per page | 469 | 599 |
| Service detail pages | 13 | 15 |
| Avg words per service page | 411 | 276 |
| Doctor profile pages | 1 | 6 |
| SEO location pages | 0 | 6 |
Content Depth by Category
| Category | MPMC Pages | MPMC Words | SHD Pages | SHD Words |
|---|---|---|---|---|
| Core pages | 7 | 4,223 | 7 | 8,097 |
| Service detail pages | 13 | 5,350 | 15 | 4,146 |
| Doctor profiles | 1 | 290 | 6 | 2,732 |
| SEO location pages | 0 | 0 | 6 | 5,376 |
The reference site has 2x the raw word count, but 26% of that content (5,376 words) comes from 6 SEO location pages that duplicate the same practice description with different suburb keywords. MPMC’s service pages are 49% more verbose on average (411 vs 276 words), providing deeper clinical content per specialty — the kind of content that builds E-E-A-T signals and answers patient questions directly.
Structured Data Quality
| Feature | MPMC | Surry Hills Drs |
|---|---|---|
| JSON-LD schema type | MedicalOrganization | WebSite + LocalBusiness |
| Coverage | 100% of pages | 100% of pages |
| Meta descriptions | 100% (21/21) | 94% (32/34) |
| Open Graph tags | 7 per page (consistent) | 4-8 per page (inconsistent) |
| URL structure | Hierarchical (/services/[slug]) | Flat (/[slug]) |
MPMC uses MedicalOrganization — the healthcare-specific JSON-LD type that Google explicitly supports for medical practice rich results. The reference site uses generic LocalBusiness, which misses healthcare-specific properties like medical specialties, practitioner details, and available services.
Security Posture
| Header | MPMC | Surry Hills Drs |
|---|---|---|
| HSTS | 2 years + preload | 180 days, no preload |
| Content-Security-Policy | Full CSP with explicit allowlists | Not present |
| X-Content-Type-Options | nosniff | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin | Not present |
| Cross-Origin-Opener-Policy | same-origin | Not present |
MPMC ships 5 security headers including a full Content-Security-Policy. The reference site has 3 basic headers with no CSP — standard for Squarespace, which doesn’t expose CSP configuration. For a medical practice handling patient contact information, the CSP prevents cross-site scripting attacks and limits which domains can execute code on the page.
SEO & Search Visibility
Coming soon — this section will be updated with real search performance data once the site has had sufficient time to be indexed and ranked by Google.
What We’re Tracking
- Google Search Console — connected via URL prefix verification on launch day
- Target keywords — “bulk billing GP Macquarie Park”, “doctor Macquarie Park”, “GP near Macquarie University”
- Suburb coverage — 8 suburbs in structured data: Macquarie Park, North Ryde, Ryde, Marsfield, Eastwood, Epping, Lane Cove, Chatswood
- Indexation — monitoring page-by-page index status and crawl coverage
- AI Overview appearances — tracking whether the MedicalOrganization schema and service content appear in Google AI Overviews for local medical queries
SEO Foundations Already in Place
| Foundation | Status |
|---|---|
| JSON-LD MedicalOrganization schema | Deployed on all 21 pages |
| Sitemap submitted to GSC | Verified |
| Canonical URLs | Clean, trailing-slash normalised |
| Meta descriptions | 100% coverage, 80-200 characters |
| Open Graph + Twitter Cards | All pages, consistent |
| Mobile-first indexing ready | Lighthouse mobile performance 95+ |
| Page speed for Core Web Vitals | All metrics green |
| Internal linking structure | Hierarchical /services/[slug] |
Planned SEO Expansion
- Individual doctor profile pages — each doctor as a separate indexable route with schema markup
- Suburb-targeted landing pages — dedicated pages for the 8 target suburbs, each with unique content (not duplicate copy)
- Google Business Profile optimisation — linking structured data to GBP listing
- FAQ schema — existing FAQ content in Sanity to be marked up with
FAQPagestructured data - Review integration — Google review widget and
AggregateRatingschema
We’ll update this section with actual ranking data, search impression graphs, and click-through rates once we have 90 days of Google Search Console data (target: May 2026).
The Result
The site went live on February 16, 2026. Within the first week, the practice confirmed that online bookings through HotDoc had increased — patients were finding the practice through search and booking directly from the site rather than calling during business hours.
- 7 static pages + dynamic doctor profiles and service detail routes generated from Sanity CMS data
- 12 content schemas giving the practice full editorial control without developer dependency
- 14 medical service categories with custom branded icons replacing an 822KB icon font
- 6 booking touchpoints ensuring patients always have a clear path to HotDoc
- 8 suburbs targeted in structured data for local search visibility
- Automated CI/CD — content changes in Sanity trigger rebuild and deploy within minutes
- Bulk billing communicated at every level: top bar badge, hero, dedicated card, footer, welcome popup
- Multilingual doctor profiles with language tags, qualifications badges, and availability status
- Zero ongoing developer dependency — the practice team manages content, doctors, services, and FAQ independently
- Full accessibility — reduced-motion support, ARIA labels, focus states, semantic HTML, skip-to-content navigation
Performance Benchmark
Side-by-side comparison against a Squarespace medical practice — same specialty, same city, different architecture.
Lighthouse Audit Scores
Core Web Vitals
Total Page Weight
Content Architecture
Tech Stack
Project Highlights
Headless Architecture
Astro SSG with Sanity CMS backend. Content editors update through a hosted studio — changes trigger automatic rebuilds via webhook.
12 content schemasPerformance Engineered
Self-hosted variable font (eliminates Google Fonts waterfall), inline SVG sprite replaces 822KB icon font, critical CSS inlined.
Sub-second TTFBMulti-Layer Parallax
Hero images, CTA bands, and section backgrounds use requestAnimationFrame-throttled parallax. Gracefully disabled on touch devices.
Automated Deployment
GitHub Actions with sparse checkout builds only the site directory. Cloudflare Pages direct upload with custom domain and apex redirect.
Zero-touch deploys"The team delivered a website that perfectly represents our practice. The online booking integration and content management system have made it effortless for our staff to keep everything up to date."
Ready to build something like this?
Let's discuss your project requirements and how we can help.
Start a Project