DEV Community

aimall
aimall

Posted on

Building an Evidence-Based Medical Q&A Site with Go — Architecture, Structured Data, and an SEO Playbook

Building an Evidence-Based Medical Q&A Site with Go

A practical walkthrough of CloudKnow (云知道) — a Chinese-language evidence-based medical Q&A platform we built with Go. It now serves 112 evidence-backed clinical Q&As, a 91-disease knowledge base, 1,300+ treatment plans, and 1,000+ drug monographs, with every answer graded on a 6-level evidence scale (from RCT/meta-analysis down to expert opinion).

If you're building any content-heavy site that depends on organic search traffic, the SEO half of this post applies to you regardless of stack.

Why another medical site?

Most Chinese health content on the open web is either SEO spam or forum anecdotes. We wanted pages where every treatment recommendation carries an explicit evidence grade — A (RCT/Meta) down to E (Expert opinion) — so readers can see how strong the recommendation is, not just what it says.

The content pipeline ingests structured clinical summaries (modeled on BMJ Clinical Evidence-style grading), renders them to HTML server-side, and publishes them as plain, crawlable pages. You can see an example disease page here: 2型糖尿病 (Type 2 Diabetes) — 22 graded treatment measures, each with its own anchor link and evidence filter.

The stack

Deliberately boring, single binary, no build step:

  • Go + Gin + sqlx + PostgreSQL — one static binary, cross-compiled with CGO_ENABLED=0, deployed via systemd on a single HK box behind Cloudflare.
  • html/template + //go:embed — templates and static assets are embedded in the binary. Rebuilding = redeploying one file. No CDN dependency for frameworks.
  • goldmark + bluemonday (UGCPolicy) — user/LLM-generated markdown is rendered to HTML and then strictly sanitized. Never skip the sanitizer step for content that wasn't hand-written by trusted staff.
  • Self-hosted Bootstrap/jQuery — we pulled Bootstrap 5.3, bootstrap-icons, and jQuery into the binary via embed.FS and served them from /static/* with Cache-Control: public, max-age=31536000, immutable. This removed a third-party CDN dependency (a real supply-chain and availability risk for China-adjacent traffic) and gave us a 100% first-party asset chain.

The SEO playbook (the part most Go projects skip)

Server-rendered HTML from html/template is already crawlable — but crawlable is not the same as rankable. What we added:

1. Structured data that actually matches the page type

Every Q&A page emits a FAQPage JSON-LD block generated from the question title and the cleaned first answer:

// schema.org FAQPage — helps Google render rich results
func FAQJSONLD(question, answer string) template.HTML {
    // strip markdown headings/lists down to plain text (≤300 chars),
    // then marshal {"@context":"https://schema.org","@type":"FAQPage",...}
}
Enter fullscreen mode Exit fullscreen mode

Disease pages use MedicalWebPage-style meta instead. Matching the schema type to the actual page semantics beats sprinkling generic WebSite schema everywhere.

2. Meta hygiene per page, not site-wide

  • meta description generated from the answer's opening text (before the first ## heading, markdown stripped, ≤300 chars)
  • canonical URLs built from the real public origin (mind your reverse-proxy setup — derive from config, not Host headers)
  • Open Graph + Twitter Card tags for social sharing

3. A dynamic sitemap.xml that queries the DB

Instead of regenerating a static file on every content change, we serve /sitemap.xml straight from PostgreSQL:

r.GET("/sitemap.xml", webH.Sitemap) // SELECT id FROM diseases / questions / drugs
Enter fullscreen mode Exit fullscreen mode

~1,300 URLs, application/xml, one hour of Cache-Control. Add Sitemap: to robots.txt and both Google and Bing pick it up without any manual submission.

4. Crawl-path hygiene

  • robots.txt blocks /api/, /portal/, /backend/ but explicitly allows GPTBot and ClaudeBot on public medical pages — if you want to be cited by AI answer engines, let their crawlers in.
  • Fix content that renders but can't be scanned: our original homepage had a flexbox bug that left the main content a 0-height flex child. Chrome headless screenshots caught it; crawlers would have seen an empty page.

Results and honest caveats

The site went from "content exists" to "technically indexable" in one working session: sitemap live, JSON-LD validated, no external CDN, immutable asset caching, per-page meta.

Caveats worth stating plainly:

  • Medical content needs the evidence grades to be more than decoration. Ours map to source citations in the DB; if your structured data claims FAQPage but the page is marketing copy, that's a (deserved) manual-action risk.
  • JSON-LD rich results are a long game for YMYL (your-money-or-your-life) topics. Expect weeks, not hours, before search engines trust a new domain on health content.
  • The dev.to/blog cross-posting only works if the target article is genuinely useful — which is why this post is a build log, not an ad.

Try it

The site is live at rws.souyi.net.cn — browse the disease library or a sample graded Q&A page. If you're building something similar in Go, happy to answer questions about the goldmark/bluemonday pipeline or the structured-data setup in the comments.

Related from the same team: AIMALL — an AI tool marketplace with a verifiable trust layer for agent tool calls.

Top comments (0)