Case Studies

Making a vet clinic citable by ChatGPT, Perplexity, and Claude

What we shipped on sixteenmilevet.com to make the site eligible for citation by AI answer engines: robots policy, llms.txt, reviewer-aware E-E-A-T, schema hygiene, and Core Web Vitals.

ByAdpharm Digital||6 min read
Reviewed byBen Honda

We shipped a coordinated AI-EO + technical SEO pass on sixteenmilevet.com: allow citation-pathway AI bots and block training-only ones, expose /llms.txt and /pricing.md at the root, harden authorship and reviewer JSON-LD, replace the hand-rolled sitemap with one derived from the router, and lift Core Web Vitals on the homepage. The work shipped today; we will update this post with measured results in a few weeks.

Sixteen Mile Veterinary Clinic is a single-location practice in Oakville. The site runs on Astro 5 (server output), React 19, and Tailwind 4, deployed to Vercel. The brief was simple: make the site eligible for citation by AI answer engines, tighten the technical-SEO surface, and lift Core Web Vitals on the homepage.

Most of the value was in the order. We started with the crawler policy because a site no engine can reach is invisible no matter how clean its schema is. Authorship came second, because Google’s vet-content guidance is strict and the answer engines are converging on the same signals; vague attribution costs rich-result eligibility no matter how fast the page loads. With those two layers right, schema and sitemap hygiene fall into place, because every other signal compounds once the entity model is consistent. Performance came last. It is the most familiar lever, and the smallest one when the layer underneath it is broken.

Crawler policy: opt in to citation, opt out of training

We rewrote robots.txt around a single question: does this bot send users back to the source, or does it just train a model?

  • Allowed: GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-Web, anthropic-ai, PerplexityBot, Perplexity-User, Google-Extended, Applebot, Applebot-Extended, Bytespider, Meta-ExternalAgent, Amazonbot.
  • Blocked: CCBot. It feeds Common Crawl, a training corpus with no citation pathway, so allowing it costs bandwidth without earning visibility.
  • Crawl-delay: SemrushBot, AhrefsBot, DotBot. Useful tools, kept off the critical path.

This is the cheapest change in the pass. Most clinic sites land at one of two extremes: every bot blocked, or every bot allowed.

/llms.txt and /pricing.md

Two flat files now sit at the root of the site, both linked from the sitemap.

/llms.txt follows the proposed standard for giving language models a clean, low-noise summary of the site. Ours covers location, hours, team, plan structure, key URLs, and the disclaimer. A model grounding against the file reads that framing instead of whatever it would otherwise piece together from the marketing pages.

/pricing.md is the wellness-plan pricing in machine-readable Markdown: SMVC Club at $40/month, the P.A.L. Plan, exam fee, plan rules. Pricing is the single most-asked question across vet AI queries, and serving it as plain text lets answer engines quote it accurately rather than reconstruct it from scraps.

Authorship and E-E-A-T

Google’s vet-content guidance is strict, and the answer engines are converging on the same signals.

We built a per-post author registry. Posts written by credentialed staff (Dr. ... DVM) emit Person JSON-LD with the clinic as affiliation; non-credentialed authors fall back to Organization.

Many of the 92 educational posts were drafted by the editorial team and reviewed by a clinician. The schema layer now enforces that every non-draft post carries either an author or a reviews array. Reviewer-only posts render “Reviewed by Dr. …” in the byline and emit reviewedBy Person entries alongside the clinic as author.

A /disclaimer page plus a per-post editorial disclaimer aside, linked from the footer, tells humans and crawlers that the articles are educational and not a substitute for an exam.

Structured-data hygiene

The schema layer needed several small fixes, each of which carries an outsized effect on how Google and the answer engines reconcile entities across the site.

We canonicalised every JSON-LD URL to https://www.sixteenmilevet.com so BlogPosting, the sitemap, and <link rel="canonical"> agree. Mismatched hosts (apex vs. www) silently demote rich-result eligibility.

We removed a stale specialOpeningHoursSpecification for Canada Day 2025 that was still being emitted in 2026. A schema that contradicts the visible page is worse than no schema, because the engine has no way to know which to trust.

We added optional faq frontmatter on blog posts that emits FAQPage JSON-LD. The hardcoded “Sarah Bishop welcome” FAQ block was the first consumer.

We replaced ad-hoc breadcrumb components with BreadcrumbList JSON-LD on detail pages, plus a real <nav aria-label="Breadcrumb"> in the markup. A dead BreadcrumbsContentPages component that was imported but never rendered got deleted on the way through.

One sitemap, derived from the router

The previous setup had sitemap-index.xml.ts + sitemap-0.xml.ts plus a hardcoded list of blog slugs. We replaced both with a single sitemap.xml.ts driven by the router:

const decisions: Record<keyof typeof routes, SitemapDecision> = { ... }

The change has two consequences worth flagging. Adding a route without a sitemap decision is now a TypeScript error, which catches the most common cause of stale sitemaps. And because blog posts come from the content collection, new articles appear automatically, including the new /blog/<n> and /blog/topic/<slug>[/<n>] paths.

Astro footnote: pagination pages originally used getStaticPaths, which never runs under output: "server". We swapped to request-time Astro.params.page parsing with a redirect-to-page-1 fallback for out-of-range values.

Topic-based blog archive

The blog grew to 92 posts across ticks, heartworm, fleas, dog allergies, safe foods, and clinic updates. We replaced the flat archive with:

  • /blog, paginated 12 per page, with a topic-chip filter row.
  • /blog/topic/<slug> archives for each topic, also paginated.
  • A single TOPICS registry (src/lib/blog/topics.ts) feeding archive pages, post breadcrumbs, the sitemap, and the footer column. One file to update; everything else stays in sync.
  • Featured-post hero on /blog; a “Continue Reading” block on each post with sessionStorage-backed visited-state highlighting.

The topic archives matter for AI-EO specifically: they give answer engines clean topical hubs to cite when a user asks a category-shaped question like “tick prevention in Ontario” rather than a long-tail one.

Dynamic Open Graph images

Every page now has its own 1200×630 OG card, generated on the fly via @vercel/og:

  • The /og/<path>.png endpoint resolves the title and category label from a server-side registry keyed by path. Nothing about the rendered card comes from the query string. The endpoint cannot be coerced into rendering attacker-controlled text on a sixteenmilevet.com URL.
  • SEOHead falls through: explicit image prop, then registered dynamic OG, then static /ogimage.png.
  • Vercel includeFiles bundles the brand fonts and white-logo SVG into the serverless function so the renderer is self-contained.

Every share, link preview, and og:image lookup ends up with a branded card without anyone hand-designing one per page.

Core Web Vitals on the homepage

LCP and CLS feed into both classical SEO and the freshness of any AI summary that re-fetches the page.

  • We preloaded Lexend Deca and Open Sans Latin woff2 subsets in the base Layout so above-the-fold text never blocks on font fetch.
  • We preloaded the homepage hero image with fetchpriority="high". Below-the-fold <img> tags get loading="lazy" and decoding="async".
  • We converted the Get-to-Know-Us carousel’s first slide from a CSS background-image to a real <img> so it can be marked high-priority and sync-decoded.
  • We replaced the hero’s .jpg with a hand-tuned .webp (101 KB) for an immediate byte-size win.

What we are tracking

The work shipped today (2026-05-09). Results take weeks, so we will come back and replace this section with a measured update. The watchlist:

  • Citation rate in AI answer engines, tracked through ChatGPT search, Perplexity, and Claude search for vet-pricing and topic-shaped queries. We expect /pricing.md and the topic archives to surface first.
  • Rich-result eligibility in Google Search Console for BlogPosting, FAQPage, BreadcrumbList, and LocalBusiness. The Canada-Day-2025 stale-hours fix should clear an existing warning.
  • Indexed-page count. With the router-driven sitemap, the new topic archives and pagination pages should appear in coverage reports within the first crawl cycle.
  • Core Web Vitals on the homepage. LCP is the one to watch; the hero .webp and font preload should move it.

Sources

Related

Keep reading

AI & Tech

Claude Design produces AI slop unless you tell it not to

Anthropic's new design tool ships with anti-slop guardrails. They don't fire on their own. The DESIGN.md, system prompt, named aesthetic, and reference-mix that actually do.

·10 min

AI & Tech

A working playbook for Claude Code Skills on Opus 4.7

Skills are markdown files Claude reads when relevant. The description field decides whether one ever triggers. What changed in Opus 4.7, and the trap that silently disables half your skills.

·10 min

AI & Tech

Two Google image models, two jobs: a working prompt guide for Nano Banana Pro and Nano Banana 2

Google ships two image models on Gemini 3 now: Pro for hero shots, NB2 for everything else. The original Nano Banana's prompting habits actively hurt; here's the working stack.

·10 min

AI & Tech

Brand-fidelity mockups in Claude Code and Google Stitch: what actually steers them off the AI default

Both tools default to the same generic AI aesthetic: Inter, purple-on-white, three-up icon cards. A practitioner playbook for steering each off it, and the cross-tool file doing most of the work.

·10 min

AI & Tech

How to get Claude Opus 4.7 to write copy that doesn't sound like AI

A working playbook for stopping Claude from defaulting to LinkedIn-thinkpiece prose: the leverage points, the banned-words block, the three-pass workflow, and the before/after.

·10 min

Compliance

Google's 2025 HCP targeting changes, read for Canadian pharma

Between May and October 2025, Google opened personalised targeting of healthcare professionals. What that means for Canadian pharma working under PAAB and the Food and Drugs Act.

·9 min
© 2026 The Adpharm. All rights reserved.