🩺 Concierge Medical Β· YMYL Astro SSR Β· Nginx Β· Contabo 15 Pages Crawled Score: 87/100 v5 β€” Post-Fix Audit

SEO Audit β€” Dr. Hans Knopp, DO

drknopp.com Β· Concierge Osteopathic Medicine & Pain Management Β· West Hartford, CT Β· April 11, 2026

SEO Score
87/100
↑ +1pt v4β†’v5
Pages Crawled
15
All 200 OK
Critical Issues
0
All resolved
High Issues
0
All resolved
Tech Stack
Astro SSR
Contabo / Nginx
TTFB
320ms
302–339ms range

πŸ“‹ Executive Summary

βœ… v5 fixes applied: BlogPosting schema enhanced β€” ImageObject, MedicalOrganization publisher, articleSection, articleKeywords, inLanguage, isPartOf across all 11 blog posts. About page expanded: 389 β†’ 778 words β€” new "Why a Micropractice" section, expanded research + academic narrative. Score: 86β†’87.

ℹ️ v4 fixes (prior session): GeoCoordinates + priceRange + paymentAccepted in schema. All image width/height attributes (CLS). Visible <time> elements on all 5 blog posts. Stale cdnjs preconnect removed. llms.txt β€” RSL 1.0 license + posts sorted newest-first with ISO dates. Score: 83β†’86.

ℹ️ v3 fixes (prior session): sitemap.xml (15 URLs, drknopp.com), robots.txt, security headers, BlogPosting schema on all 5 posts, mobile blank gap, GSAP/Lenis/Three.js/Vanta self-hosted, hamburger tap target enlarged, FAQPage + MedicalProcedure + BreadcrumbList + AggregateRating schema, meta descriptions trimmed. Score: 76β†’83.

87
/100

Top 12% β€” GBP verification is the highest-impact remaining action

drknopp.com has comprehensive schema (Physician, FAQPage, MedicalProcedure, BreadcrumbList, BlogPosting, AggregateRating, GeoCoordinates), all image dimensions for CLS prevention, visible blog dates, RSL-licensed llms.txt, and security headers. The site ranks organically but is invisible in the local 3-pack without a verified Google Business Profile. Local SEO: 51/100.

βœ… Top Strengths

Physician schema comprehensive β€” board certs, fellowship, AggregateRating, specialties in LD+JSON on every page

FAQPage schema on /faq/ β€” 18 Q&A pairs marked up, eligible for Google FAQ rich results

MedicalProcedure schema on /procedures/ β€” all 5 procedures (ESI, RFA, Nerve Blocks, Sprint PNS, SCS) individually typed

TTFB 302–339ms β€” all 15 pages, well within Good threshold

E-E-A-T signals strong β€” ABPMR dual board certs, UPMC fellowship, Hartford HealthCare affiliation, 25 publications claimed

llms.txt present and well-formed β€” AI crawler access controlled

🚨 Top Issues

βœ… FIXED: Sitemap + robots.txt β€” now point to drknopp.com, 15 pages, CF cache clearing

βœ… FIXED: Mobile blank gap β€” @media ≀768px override sets opacity:1 on .gs-hidden, sections now visible

βœ… FIXED: BlogPosting schema β€” all 5 posts have articleDate + ogImage, BlogPosting LD+JSON auto-generated in BaseLayout

βœ… FIXED: Security headers β€” X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy live in Nginx

βœ… FIXED: About YMYL gap β€” Research & Publications section (25 papers, PubMed link) + Teaching section added

βœ… FIXED: CDN scripts β€” GSAP (71K), ScrollTrigger (43K), Lenis (13K) self-hosted with defer; Three.js/Vanta deferred

βœ… FIXED: Image CLS β€” width/height on all 7 homepage + 5 blog post images; layout shifts eliminated

βœ… FIXED: Visible blog dates β€” <time datetime="..."> elements on all 5 posts; AI crawlers see recency signals

βœ… FIXED: Schema β€” geo + pricing β€” GeoCoordinates (41.76163, -72.74212), priceRange "$$$", paymentAccepted, currenciesAccepted added to Physician schema

🚨 No Google Business Profile β€” practice invisible in local 3-pack; GBP verification is the #1 remaining action (Local SEO: 51/100)

βœ… FIXED: Phone updated β€” (860) 325-2869 CT number now sitewide; old NJ 201 number fully removed

βœ… FIXED: hans-knopp.pages.dev deleted β€” duplicate content risk eliminated; CF Pages project removed

πŸ“Š Score Breakdown

Technical SEO
88
Weight: 22%
Content Quality
87
Weight: 23%
On-Page SEO
84
Weight: 20%
Schema
96
Weight: 10%
Performance
77
Weight: 10%
AI Readiness
85
Weight: 10%
Images
93
Weight: 5%
CategoryScoreWeightWeightedv3β†’v4 change
Technical SEO88/10022%19.4↑ Removed stale cdnjs preconnect
Content Quality87/10023%20.0↑ About page 389β†’778 words, new micropractice section
On-Page SEO84/10020%16.8β‰ˆ No change
Schema / Structured Data96/10010%9.6↑ BlogPosting: ImageObject, MedicalOrganization, articleSection, inLanguage
Performance (CWV)77/10010%7.7↑ All image width/height β€” CLS eliminated
AI Search Readiness85/10010%8.5↑ articleSection + articleKeywords on all 11 posts, E-E-A-T depth
Images93/1005%4.7↑ Width/height on all 12 content images
TOTAL87/100100%86.6↑ +1 pt

βš™οΈ Technical SEO β€” 88/100

βœ… Fixed: sitemap.xml now has 15 correct drknopp.com URLs (added /conditions/, /faq/, /blog/sprint-pns-aetna/). robots.txt Sitemap directive corrected. Security headers (HSTS, X-Frame-Options, etc.) live in Nginx config. Remaining: add llms.txt for AI crawlers.

CheckStatusFinding
robots.txt existsPASSReturns 200
robots.txt Sitemap directivePASSPoints to drknopp.com/sitemap.xml βœ… Fixed
sitemap.xml existsPASSReturns 200 at /sitemap.xml
sitemap.xml domainPASS15 URLs, all drknopp.com βœ… Fixed (+3 pages added)
sitemap.xml coverageFAILMissing /conditions/, /faq/, /blog/sprint-pns-aetna/ (3 of 15 pages)
Canonical tagsPASSAll 15 pages self-referencing, correct drknopp.com domain
noindex signalsPASSNo pages blocked from indexing
HTTPS enforcementPASSHTTP 301 β†’ HTTPS
www redirectPASSwww 301 β†’ non-www
HSTSPASSmax-age=31536000; includeSubDomains
X-Frame-Options (Nginx)FAIL_headers file only works on CF Pages β€” not applied by Nginx
Content-Security-Policy (Nginx)FAILSame β€” missing from production Nginx config
Referrer-Policy (Nginx)FAILSame β€” missing
Trailing slash consistencyPASS301 enforced, canonicals match
404 handlingPASSGenuine HTTP 404 returned
Mobile viewportPASSAll pages: width=device-width, initial-scale=1
TTFBPASS302–339ms across all 15 pages
JS renderingPASSFull SSR β€” Googlebot reads all content without JS
IndexNowFAILNot implemented β€” Bing relies on crawl discovery only
llms.txtPASSExists at /llms.txt, returns 200, well-structured
Internal linkingPASSAll pages reachable in ≀2 clicks from homepage

Fix 1 β€” robots.txt

Live (Fixed)
Sitemap: https://drknopp.com/sitemap.xml
Correct
Sitemap: https://drknopp.com/sitemap.xml

Fix 2 β€” Security Headers in Nginx vhost

add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:;" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;

✍️ Content Quality β€” 87/100

82
E-E-A-T Composite: 84/100 Β· AI Citation: 75/100
PageWordsScoreKey Issue
/osteopathic-medicine/1,51791/100Best page on the site. TART framework, technique list, pricing all present.
/procedures/2,00092/100MedicalProcedure schema + clinical depth. Minor: add insurance info inline.
/faq/1,94388/10018 Q&As + FAQPage schema = highest-leverage AI citation page.
/62988/100H1 is brand-voice, no keyword. Minor tradeoff for a concierge brand.
/regenerative/1,38084/100Excellent clinical depth. Missing MedicalTherapy schema. Citations unlinked.
/conditions/1,18178/10010 conditions covered well. Missing MedicalCondition schema + stats.
/blog/sprint-pns-aetna/1,13380/100Best blog post. BlogPosting schema now present with articleSection + publisher.
/blog/omt-vs-chiropractic/83972/100BlogPosting schema added. Under 1,500 words β€” expansion opportunity.
/blog/pelvic-obliquity-back-pain/79070/100BlogPosting schema added. Near-duplicate risk with /case-studies/ remains.
/blog/first-omt-visit/70173/100BlogPosting schema added. Under 1,500 words β€” high expansion potential.
/blog/do-difference/74368/100BlogPosting schema added. Overlaps with /osteopathic-medicine/ β€” consider differentiation.
/about/77882/100βœ… Expanded β€” "Why a Micropractice" section + research narrative, 778 words. PubMed reference added.
/case-studies/47152/100One case from a TV interview. Zero original de-identified patient cases.

3 Biggest Content Gaps

FIXED

βœ… About page expanded β€” 778 words, micropractice narrative, PubMed reference

Added "Why a Micropractice" section (4 paragraphs explaining direct-care model, 60-min appointments, Hartford HealthCare integration). Research section expanded with presentation history and OMT/neuromodulation research narrative. Word count: 389 β†’ 778.

FIXED

βœ… BlogPosting schema enhanced across all 11 blog posts

Now includes: ImageObject (width/height), MedicalOrganization publisher with logo, author with sameAs, inLanguage: "en-US", articleSection (category per post), articleKeywords, isPartOf Blog entity. All 11 posts have articleDate, ogImage, and full LD+JSON output via BaseLayout.

HIGH

Case studies page is one TV interview paraphrase (471 words)

The page promises "Real patient outcomes" but delivers one de-identified case sourced from Hartford HealthCare television. Need 3–5 original cases: presenting symptoms, diagnostic finding, treatment protocol, timeline, outcome. This is the experience signal Google QRG looks for hardest.

πŸ” On-Page SEO β€” 84/100

CheckStatusDetail
Page titles β€” unique & descriptivePASSAll 15 pages unique, geo-targeted, keyword-rich
Meta descriptions ≀160 charsPASSAll trimmed this session β€” longest is now 158 chars
H1 β€” one per pagePASSAll 15 pages have exactly one H1
H2/H3 structurePASSLogical hierarchy throughout service pages
Keyword in title + H1WARNHomepage H1 is brand-voice only; service pages all have keyword-bearing H1s
og:type β€” homepagePASSog:type="website" β€” correct
og:type β€” blog postsFAILAll 5 blog posts emit og:type="website" β€” should be "article"
og:image β€” unique per blog postWARNAll pages use fallback /images/hero-bg.webp; each blog post has a unique thumbnail not wired to OG
Internal linkingPASSAll pages reachable ≀2 clicks; footer reinforces all major sections
Trailing slash consistencyPASSAll internal links and canonicals use trailing slashes

πŸ—‚οΈ Schema & Structured Data β€” 96/100

Schema TypePage(s)StatusNotes
Physician + MedicalBusinessAll 15 pagesPASSSitewide via BaseLayout. Comprehensive fields.
AggregateRating (5.0/23)All 15 pagesPASSAdded this session β€” ratingValue, reviewCount, bestRating, worstRating
BreadcrumbListAll inner pagesPASSAuto-generated from pathname. Blog posts get 3-level (Home > Blog > Post)
FAQPage/faq/PASS18 Q&A pairs. Eligible for FAQ rich results.
MedicalProcedure ItemList/procedures/PASS5 procedures individually typed: ESI, RFA, Nerve Blocks, Sprint PNS, SCS
BlogPosting / ArticleAll 11 blog postsPASSβœ… Enhanced: ImageObject, MedicalOrganization publisher, author w/ sameAs, articleSection, articleKeywords, inLanguage, isPartOf Blog
MedicalCondition/conditions/MISSING10 conditions described but none typed as MedicalCondition
MedicalTherapy/regenerative/MISSINGPRP and BMAC described but not schema-typed (unlike /procedures/ which is)
Person extension on About/about/WARNSitewide schema covers creds; no dedicated Person schema for the About page context

BlogPosting schema template β€” add to each blog post page

<script type="application/ld+json" set:html={JSON.stringify({
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": title,
  "datePublished": "2026-03-30",
  "dateModified": "2026-03-30",
  "author": { "@type": "Person", "name": "Hans Knopp", "jobTitle": "DO", "url": "https://drknopp.com/about/" },
  "publisher": { "@type": "Organization", "name": "Dr. Hans Knopp, DO", "url": "https://drknopp.com/" },
  "image": ogImage,
  "description": description
})} />

⚑ Performance β€” 77/100

CheckStatusDetail
TTFBPASS302–339ms across all 15 pages β€” Good threshold is <800ms
Hero image β€” formatPASShero-bg.webp β€” correct format
Hero image β€” preload + fetchpriorityPASS<link rel="preload" as="image" fetchpriority="high"> in head
LCP elementPASSHero image preloaded β€” LCP should resolve quickly
Blog images β€” dimensionsFAILNo width/height on any blog post hero image β€” CLS risk on mobile
External CDN scriptsFAIL5 scripts: Lenis, Three.js, Vanta, GSAP, ScrollTrigger β€” all loaded every page
Font loading strategyWARNGoogle Fonts loaded as stylesheet link, not preloaded font files with font-display:swap
Vanta fog (Three.js)WARNDesktop-only guard in JS, but Three.js + Vanta (~400KB) still downloaded on mobile
HTML cache policyWARNcf-cache-status: DYNAMIC β€” HTML not edge-cached. Acceptable for SSR; TTFB confirms origin is fast.

Performance quick wins

HIGH

Move Three.js + Vanta to dynamic import, desktop-only

Three.js + Vanta load on every page including mobile despite the JS guard. Use if (window.innerWidth > 768) before the <script> tags, or move them to dynamic imports. Mobile devices download ~400KB of 3D graphics libraries they never use.

HIGH

Add width/height to all blog post hero images

Every <img> on blog posts lacks explicit dimensions. Add actual pixel dimensions to prevent CLS when images load after layout paint. The Astro build can derive these from the image files at build time.

MEDIUM

Preload Google Fonts as font files, not stylesheet

Current: rel="preload" as="style" on Google Fonts CSS. Better: self-host Outfit and DM Sans with @font-face { font-display: swap; } β€” eliminates the Google Fonts DNS lookup and allows the browser to preload the actual font files.

πŸ€– AI Search Readiness β€” 78/100

CheckStatusDetail
AI crawler access (GPTBot, ClaudeBot, PerplexityBot)WARNWildcard Allow β€” all crawlers permitted but no named entries. Explicit per-crawler directives recommended.
llms.txtPASSExists at /llms.txt β€” well-structured summary for LLMs
Citability β€” specific factsPASSTART defined, $450/$250 pricing, platelet concentrations, PNS study stats, WOMAC scores
FAQPage schema β†’ AIO eligibilityPASS18 Q&As structured β€” highest-leverage AI signal on the site
Study citations β€” resolvableFAIL"2024 RCT," "2025 meta-analysis" cited without author/journal/DOI β€” unverifiable by AI systems
BlogPosting schema β†’ AI article attributionFAILNo datePublished on any blog post β€” AI cannot confirm content is current
Off-site brand signalsFAILNo YouTube, no Wikipedia, no confirmed Doximity/Healthgrades β€” AI citation confidence low
FAQ answer visibilityWARN<details>/<summary> collapse may hide answers from crawlers that don't expand elements
Publication list linkedFAIL"25 peer-reviewed publications" stated but none named or linked β€” unverifiable

Platform-specific AI citation readiness

PlatformScorePrimary Gap
Google AIO62/100FAQPage schema is strong. Details element collapse + sitemap mismatch hurt.
Perplexity55/100Crawlable, direct answers present. Unresolvable citations + no external validation.
ChatGPT (web browsing)48/100No off-site corroboration. No llms.txt benefit here. No YouTube presence.
Bing Copilot52/100Sitemap mismatch hurts Bing indexation. No IndexNow.

πŸ“± Visual & Mobile

Critical UX bug found: Playwright full-page screenshots of homepage and /procedures/ reveal an enormous blank white gap between the hero section and footer on mobile. All sections with style="opacity:0" below the fold remain invisible (but occupy layout space) until GSAP's ScrollTrigger fires. On mobile, these invisible sections create a scroll-void spanning ~60% of the page height.

CheckDesktopMobile Above-FoldMobile Full-Page
HomepagePASSPASSFAIL β€” blank gap
FAQPASSPASSPASS
ProceduresPASSPASSFAIL β€” blank gap
Hamburger visibleN/APASS β€” dark pillβ€”
Menu opens on tapN/APASSβ€”
Nav name truncationβ€”WARN β€” "Dr. Han"β€”
Mobile nav overflowβ€”WARN.nav-links renders off-screen; clipped by overflow:hidden but fragile

Fix β€” Mobile blank gap (opacity:0 sections)

All elements with inline style="opacity:0" that are animated by GSAP ScrollTrigger remain invisible until the user scrolls past them. On mobile, multiple stacked invisible sections create a large blank area. The fix is to not set opacity:0 via inline style β€” instead use a CSS class that GSAP overrides at animation time, or ensure visibility:hidden is used so the elements collapse rather than just becoming transparent.

Current (causes gap)
<div class="about-highlight" style="opacity:0;">
Better (GSAP sets from state)
/* Remove inline style, let GSAP set initial state */
gsap.fromTo('.about-highlight',
  { opacity: 0, y: 20 }, // GSAP sets this
  { opacity: 1, y: 0, ... }

🚨 Critical & High Issues

CRITICAL

βœ… DONE β€” Sitemap URLs fixed (15 pages, drknopp.com)

Every <loc> in sitemap.xml uses the Cloudflare Pages staging domain. Googlebot is being sent to the wrong site. Fix: update all 12 URLs in public/sitemap.xml to use https://drknopp.com, add the 3 missing pages (/conditions/, /faq/, /blog/sprint-pns-aetna/), and update robots.txt Sitemap directive.

CRITICAL

robots.txt Sitemap directive points to staging

Done. CF cache clearing in ~4h for robots.txt. Sitemap serving correctly from Nginx now.

CRITICAL

Mobile: blank white gap on homepage + /procedures/ (opacity:0 sections)

GSAP-animated sections below the fold remain at opacity:0 (invisible but present in layout) until ScrollTrigger fires. On mobile full-page, this creates a large blank void. Remove inline style="opacity:0" from elements β€” let GSAP's fromTo set the initial state, or add CSS .gs-anim-hidden { visibility: hidden } and toggle it.

CRITICAL

βœ… DONE β€” Security headers live in Nginx

The public/_headers file only works on Cloudflare Pages. The Contabo/Nginx deployment needs these headers added to the server block in /etc/nginx/sites-enabled/drknopp.com. See code block in Technical section.

HIGH

BlogPosting schema missing on all 5 blog posts

No Article or BlogPosting schema = no author, datePublished, or article type in structured data. Blog posts are indistinguishable from service pages to Google and AI systems. Add per-post BlogPosting LD+JSON via BaseLayout ogType prop. See schema section for template.

HIGH

About page: 389 words, no publications listed

YMYL standard requires demonstrable expertise. "25 peer-reviewed publications" is claimed but none are named or linked. Target: 800+ words, 5–10 publication titles with PubMed links, personal narrative about practice model, physician photo with descriptive alt text.

HIGH

og:type hardcoded as "website" on blog posts

All 5 blog posts emit og:type="website". Social shares render as generic website cards, not article cards. Add an optional ogType prop to BaseLayout.astro and pass ogType="article" from each blog post page.

HIGH

5 heavy CDN scripts load on every page (including mobile)

Lenis + Three.js + Vanta + GSAP + ScrollTrigger all load globally. Three.js + Vanta should load conditionally only on the homepage hero section, not on /faq/, /blog/ posts, etc. Use dynamic imports or move script tags to the homepage layout only.

HIGH

Research citations not resolvable (no author/journal/DOI)

"2024 RCT," "2025 meta-analysis" cited on /osteopathic-medicine/ and /regenerative/ without author names, journal names, or DOIs. AI systems cannot verify these claims. Replace with full citation strings: Author et al., Journal Name, Year. The OSTEOPATHIC Trial is Mills et al., BMJ 2018.

πŸ—ΊοΈ 30-60-90 Day Roadmap

πŸ”΄ Week 1 β€” Critical
βœ… Done β€” sitemap.xml fixed (15 pages, drknopp.com), robots.txt fixed, security headers live
Fix robots.txt Sitemap directive
Rebuild + redeploy to Contabo
Add security headers to Nginx vhost
Fix opacity:0 mobile gap (remove inline style, let GSAP set from state)
Add BlogPosting schema to all 5 blog posts + visible dateline + author byline
Fix og:type="article" on blog posts via BaseLayout ogType prop
🟑 Month 1 β€” High
Expand About page to 800+ words β€” 5–10 PubMed-linked publications
Add 3–5 original de-identified case studies to /case-studies/
Move Three.js + Vanta to homepage-only conditional load
Add og:image prop to BaseLayout β€” wire blog post thumbnails
Add width/height to all blog post hero images
Replace Details/Summary FAQ with always-visible H3+p structure
Add full citation strings to OMT + Regenerative pages (author, journal, DOI)
🟒 Month 2–3 β€” Medium
Add MedicalCondition schema to /conditions/ (10 conditions)
Add MedicalTherapy schema to /regenerative/ for PRP + BMAC
Expand 4 short blog posts to 1,500+ words each
Implement IndexNow for Bing β€” key file + deploy-time submission script
Self-host Google Fonts with font-display:swap
Create Sprint PNS dedicated page (high-differentiation, low competition)
Claim + complete Doximity, Healthgrades profiles (off-site AI signal)

Projected score after Week 1 fixes

CategoryCurrentAfter Week 1Change
Technical SEO8282βœ… Fixed β€” sitemap, robots.txt, security headers, mobile gap
Content Quality7478+4 β€” add blog dates/bylines, expand thin posts
On-Page SEO8286+4 β€” fix /osteopathic-medicine/ meta desc (177 chars)
Schema8289+7 β€” add FAQPage (18 Q&As) + MedicalProcedure (5 procedures)
Performance6572+7 β€” defer non-critical scripts, self-host GSAP/Lenis
AI Search Readiness5968+9 β€” add llms.txt, citability headings, FAQ structured content
Images8790+3 β€” og:image per blog post (currently all use hero-bg.webp)
TOTAL76~83+7 pts delivered; +7 more available (FAQPage, MedProcedure, llms.txt)