Align San Diego Family Chiropractic
A 44,000-line Next.js healthcare platform serving 5,000+ families — built solo from schema design to production deployment.

The Situation
A San Diego chiropractor with 17 years of practice and 5,000+ patients had outgrown her WordPress site. She needed a platform that could handle insurance verification with document upload, programmatic SEO for 15+ neighborhoods, a Sanity-powered blog with AI-assisted content, and a transactional email system that replaced three separate tools.
The constraint: she's the only person who edits content. The CMS had to be intuitive enough that a non-technical practitioner could manage 158 pages of content without developer support. Every architectural decision flowed from that constraint.
Solo Developer (design → deploy)
2025 – Present
Healthcare practice · San Diego, CA
System Architecture
The system coordinates 10+ external services through 53 API routes. When content publishes in Sanity, the revalidation webhook invalidates caches and fires IndexNow submissions to Bing — search engines learn about changes in seconds, not days. The core architectural principle: every form submission passes through the same security pipeline (honeypot → rate limit → geo-block → validate → process) regardless of which integration it triggers downstream.
Request Lifecycle
Sanity v5 with TypeGen, GROQ, Visual Editing
3-layer spam prevention before any data reaches the server
81 JSON-LD schemas + programmatic area pages
OpenAI structured output for document OCR + content enhancement
27 React Email templates triggered by 8 different events
Sentry (edge + server), PostHog analytics, Slack notifications, custom audit scripts
Insurance Verification
Orchestrating 7 Services in 60 Seconds
The Problem
Patients called the front desk to verify insurance coverage, consuming 15-20 minutes of staff time per inquiry. The practice needed a self-service flow that collected patient data, verified insurance documents with AI, created a CRM contact, sent confirmation emails, and notified staff — all without the patient picking up the phone.
The form collects patient info, insurance details, and card photos across a multi-step UX with per-step validation using React Hook Form and Zod. Each step validates independently before advancing.
Uploaded insurance card images are sent to OpenAI GPT-4o Vision with response_format: json_schema — not free-text extraction, but Zod-validated structured output. The schema constrains what the model can return, eliminating hallucinated fields entirely.
Downstream orchestration runs in parallel: CRM contact creation in GoHighLevel with deduplication by email, 4 separate emails (patient confirmation, staff notification, bizops summary, failure alerts), and a Slack notification — all wrapped in a 60-second maxDuration API route.
The security pipeline runs before any of this: honeypot multi-field validation, rate limiting, IPHub geo-blocking. A spam submission never touches OpenAI or the CRM.
Partial Success > Total Rollback
The hardest part of multi-service orchestration isn't calling the APIs — it's deciding what happens when service #4 of 7 fails. I built each downstream call to be independently failable: if the CRM call fails, the patient still gets their confirmation email and staff still gets the Slack notification. Partial success is better than total rollback for a non-transactional flow.

JSON-LD @graph Composer
81 Files That Google Actually Reads
The Problem
Healthcare SEO depends heavily on structured data — Google uses it to display rich results, verify medical authority (E-E-A-T), and understand service areas. Most sites inline a single JSON-LD block per page. AlignSD needed 25+ Schema.org types (Organization, Physician, MedicalOrganization, FAQPage, etc.) composed dynamically per page, with medical codes (ICD-10, SNOMED-CT) attached to condition pages.
A 5-layer architecture instead of a single generateJsonLd() utility: the service page for "Prenatal Chiropractic" needs Organization + Physician + MedicalBusiness + Service + BreadcrumbList + FAQPage, while a blog post needs BlogPosting + Author + BreadcrumbList. Different pages compose different subsets.
Layer 1 (Core) defines TypeScript interfaces matching Schema.org spec plus business constants like address, phone, and coordinates. Layer 2 (Elements) provides reusable fragments — openingHours(), address(), aggregateRating() — called by multiple schemas.
Layer 3 (Schemas) contains full entity generators — organizationSchema(), physicianSchema(), etc. Layer 4 (Builders) maps Sanity documents to the right schema combinations. Layer 5 (Composers) assembles a @graph array from multiple schemas and deduplicates by @id.
The test suite validates every schema against Schema.org spec before deployment. Adding a new page type means adding a builder — the schemas, elements, and composer layers are untouched.
The Abstraction That Pays For Itself
The composable architecture cost ~3x more upfront time than inlining JSON-LD per page. It paid for itself the first time the client added a new service category: zero frontend changes, the builder + composer automatically generated the right structured data from the Sanity schema. The abstraction boundary is "what data exists" (Sanity) vs. "what Google needs to see" (JSON-LD) — and those two things should never be coupled.

Programmatic Area SEO
Owning Local Search Without Duplicate Content
The Problem
The practice serves 15+ San Diego neighborhoods but has one physical location. Traditional location pages are thin content farms — same text with the city name swapped. Google penalizes this. The challenge: generate unique, useful pages for each neighborhood that rank for [service] in [neighborhood] queries without triggering duplicate content penalties.
A hub page at /areas indexes all neighborhoods with a map. Spoke pages like /areas/mission-valley contain neighborhood-specific hero imagery from Sanity, local driving directions, embedded Google Map, tabbed local guide (Parks, Health & Wellness, Food, Landmarks — all unique per area), filtered patient reviews from that area, and service keyword links.
Sub-spoke pages like /areas/mission-valley/chiropractor target specific [service] + [area] long-tail keywords with unique content per combination. Each page gets its own JSON-LD (LocalBusiness + BreadcrumbList + WebPage) via the composer system.
Internal linking is bidirectional: area pages link to service pages, service pages link back to relevant areas. This creates a dense internal link graph that distributes authority across the entire programmatic SEO layer.
Content That Proves You Know the Neighborhood
The tabbed local guide (Parks, Health, Food, Landmarks) is what differentiates this from a content farm. Each tab contains hand-curated places with addresses and Google Maps links. This is the kind of content Google rewards — it's genuinely useful to someone searching "chiropractor near Mission Valley" because it proves the business actually knows the neighborhood. The CMS schema stores these as structured Sanity documents so the client can update them without touching code.

Spam Prevention
Three Walls Before the Front Door
The Problem
Healthcare contact forms are spam magnets — bots target them 24/7, and every spam submission that reaches the CRM or triggers an email costs real money (API calls, Resend sends, staff time triaging). Third-party CAPTCHA services add friction for real patients. The goal: block 99%+ of spam with zero visible user friction.
Wall 1 — Honeypot (client-side): Three invisible traps. A hidden text field (bots fill it; humans don't see it). A timing check (submissions faster than 3 seconds = bot). A hidden checkbox (bots check everything; humans never see it). Any trigger = silent rejection + spam alert email to staff.
Wall 2 — Rate Limiter (server-side): In-memory store tracking submissions per IP per time window. Exceeding the limit returns 429. No database dependency — survives cold starts.
Wall 3 — IPHub Geo-Blocking (external API): Checks the IP against IPHub's database of proxies, VPNs, datacenter IPs, and high-risk geolocations. A chiropractor in San Diego doesn't need insurance verifications from a Russian datacenter.
Only after all three walls pass does the request reach Zod validation and the actual form processing pipeline.
Silent Rejection Is the Whole Strategy
The silent rejection on Wall 1 is critical. Returning an error message tells the bot it was caught — it adapts. Returning a fake success response makes the bot think it worked. Meanwhile, the spam alert email gives staff visibility without manual triage. The three walls are ordered by cost: honeypot is free (client-side), rate limiting is cheap (in-memory), and IPHub costs an API call — so you only pay for the call on submissions that passed the first two walls.

AI Content Enhancement Pipeline
From Publish Button to SEO-Ready in Seconds
The Problem
Publishing a blog post or community event required manually writing SEO titles, meta descriptions, summaries, and category tags. For a solo non-technical editor publishing weekly content, this added 15-20 minutes per post and often resulted in suboptimal metadata that hurt search rankings.
A custom Sanity Studio document action adds an "Enhance" button to blog posts and events. One click sends the document content to an API route that orchestrates the full enhancement pipeline: content extraction, AI processing, validation, and document patching.
The API route sends the content to OpenAI with response_format: json_schema for structured output. The system prompt positions the AI as an expert SEO specialist for wellness content. Critically, it receives the full list of existing categories and tags with their Sanity _ids — the model selects from real references rather than inventing names.
The AI response is validated against a Zod schema (AIResponseSchema) before any data touches the document. A post-processing guard (isSanityDocumentId()) validates every returned reference. Any unrecognized ID is silently dropped rather than creating a broken reference in the CMS.
If OpenAI fails or returns invalid data, a fallback system truncates the content into basic SEO metadata with aiEnhancementStatus: 'failed' and aiMetadata.fallbackUsed: true. The editor always sees results — the pipeline self-heals rather than blocking the publish flow.
Constrain the Model, Trust the Output
The biggest lesson from building AI integrations: structured output + Zod validation + reference-only category selection eliminates the three most common failure modes (hallucinated fields, type mismatches, broken references). The AI never invents a category name — it picks from what exists. This makes the output trustworthy enough to patch the document automatically without human review.

React Email Template System
27 Templates, 30+ Shared Components, One Logo Change
The Problem
The practice used three separate tools for transactional email, marketing, and internal alerts. Templates were scattered across SendGrid, Mailchimp, and hardcoded HTML strings. Updating the brand logo required changes in three places, and there was no version control, no TypeScript, and no way to preview emails during development.
All 27 email templates are React components built with React Email and rendered through Resend. The templates share 30+ components — Header, Footer, Button, Logo, Divider, Badge, InfoRow, Section, CTA — so a brand update propagates everywhere from a single change.
Templates are organized by domain: 5 for insurance verification (patient confirmation, staff notification, bizops summary, spam alert, blocked alert), 4 for contact and career forms, 6 for marketing automation, and 5 for internal alerts. Each template is a typed React component that receives strongly-typed props.
The email pipeline uses Promise.allSettled for parallel sends — a failure in the staff notification email never blocks the patient confirmation. The system sends up to 5 emails per insurance verification submission, all in parallel with independent error handling.
Every email trigger is traceable: Sentry captures send failures, Slack receives alerts for blocked or spam submissions, and the bizops summary email gives the practice owner a daily digest of patient interactions.
Email Is Infrastructure, Not an Afterthought
Most projects treat email as a last-mile hack — hardcoded HTML strings or drag-and-drop builder templates that nobody version-controls. Building emails as React components with a shared component library turned email into maintainable infrastructure. The practice updated their brand colors once, and 27 templates updated in the same deploy. That's the ROI of treating email as code.

Engineering Decisions
Sanity v5 over Contentful, Payload, WordPress
Considered: Contentful (too expensive at scale), Payload (self-hosted ops burden), WordPress (not composable)
Non-technical solo editor needs: Presentation mode for visual editing, GROQ for flexible queries, TypeGen for type safety. Sanity's free tier covers the use case.
81-file JSON-LD system over a single utility
Considered: Single generateJsonLd() function, third-party schema plugin
New service categories added via CMS shouldn't require frontend deploys. The composable pattern decouples "what data exists" from "what Google sees."
In-memory rate limiter over Redis/Upstash
Considered: Redis (additional infra), Upstash (cost per call), Vercel KV (cold start latency)
A chiropractic website gets ~100 form submissions/month. In-memory with IP-based windows is sufficient. If traffic grew 100x, I'd migrate to Upstash — but premature infrastructure is a form of technical debt too.
3-layer honeypot over reCAPTCHA/Turnstile
Considered: Google reCAPTCHA (privacy concerns for healthcare), Cloudflare Turnstile (additional dependency), hCaptcha
Zero friction for patients. Healthcare users skew older — CAPTCHA challenges have measurably higher abandonment rates. The honeypot catches bots; the rate limiter + IPHub catch the rest.
React Email over MJML, Handlebars, SendGrid templates
Considered: MJML (no component model), Handlebars (no TypeScript), SendGrid drag-and-drop (no version control)
27 templates sharing 30+ components. When the practice updated their logo, one component change propagated to all 27 templates. That's not possible with HTML templates or drag-and-drop builders.
GoHighLevel CRM over HubSpot, Salesforce
Considered: HubSpot (expensive for small practice), Salesforce (overkill), custom DB
Client was already paying for GoHighLevel. Wrapping their existing CRM with typed API routes + Sentry gives observability without asking them to switch tools.
What I Learned
When orchestrating 7 external services (OpenAI, CRM, 4 emails, Slack), I stopped trying to make the entire pipeline transactional. Instead, each downstream call is independently failable. If the CRM call fails, the patient still gets their confirmation email. This reduced support tickets from "my form didn't work" to zero.
The JSON-LD composer system took 3x longer to build than inlining structured data. It paid for itself the first time the client added a new service category — zero frontend changes, the builder automatically generated the right @graph output. The rule: if the client can create new content types in the CMS, the structured data layer must handle them without a deploy.
I wrote 6 custom code audit scripts (async waterfalls, bundle imports, client component boundaries, event listeners, suspense coverage, re-render patterns) before the codebase hit 30K lines. At 44K lines, those scripts catch regressions I'd never find manually. The 2 hours invested in tooling saved 20+ hours of debugging.
By the Numbers
Lines of Code
Pages
Commits
External Integrations
JSON-LD Schema Files
Email Templates
API Routes
Test Files
Gallery






Interested in what I could build for your business? I'm currently taking on full-stack web development and AI integration projects. Read the full technical deep-dive on how I built this system.