Overview
A static site has one advantage and one constraint for SEO. The advantage: every artifact (HTML, meta, JSON-LD, sitemap, robots, llms.txt) is computed at build time, deterministic, and reviewable in a diff. The constraint: nothing is dynamic, so the build must produce every discoverability file the same as a server-rendered site would. This guide ships the full SEO surface for a Quartz site to GitHub Pages or Cloudflare Pages. Adapt the same steps for Astro, Next.js output: "export", or any other SSG.
Prerequisites
- A working static-site build (
npx quartz buildor equivalent) that emits topublic/. - A custom domain you control, or a stable subdomain on the host.
- Access to Google Search Console (GSC) for sitemap submission and post-deploy verification.
- The umbrella rules from technical read once before starting.
Steps
1. Pin the canonical base URL in build config
The build needs to know the production URL so canonicals, OG tags, sitemap entries, and JSON-LD all match. In Quartz, set baseUrl in quartz.config.ts:
configuration: {
baseUrl: "llmbestpractices.com",
// ...
}Quartz uses baseUrl to emit a CNAME, build canonical <link> tags, and write absolute URLs into the sitemap. Setting it once removes a whole class of URL-drift bugs covered in technical. See quartz-config for the full options.
2. Emit one canonical per page, self-referential
Quartz’s default emitter writes <link rel="canonical" href="https://baseUrl/slug" /> per page when baseUrl is set. Verify on a built page:
npx quartz build
grep -l 'rel="canonical"' public/*.html | head -3
grep 'rel="canonical"' public/index.htmlIf any page is missing the tag, the emitter chain in quartz.layout.ts is wrong. Add the canonical component to the head array.
3. Generate sitemap.xml at build, not by hand
Quartz emits public/sitemap.xml from every page’s frontmatter last_updated. Verify after build:
xmllint --noout public/sitemap.xml && echo "valid"
grep -c "<url>" public/sitemap.xml # should equal page countDo not hand-edit. The sitemap regenerates on every build; manual edits get overwritten and create drift.
4. Place robots.txt at the site root
Quartz reads static/robots.txt and copies it to public/robots.txt verbatim. Minimum content:
User-agent: *
Allow: /
Sitemap: https://llmbestpractices.com/sitemap.xml
Add Disallow: lines only for directories that genuinely should not be crawled (admin panels, drafts). Over-blocking is the most common static-site SEO bug.
5. Inject JSON-LD into every page
JSON-LD ships in <head> as a <script type="application/ld+json"> block. In Quartz, this is a custom component that reads frontmatter and emits the JSON. See add-jsonld-to-static-site for the full pattern. Minimum schemas to ship:
WebSiteon the home page (@type: "WebSite")Articleon every content page withheadline,datePublished,dateModified,authorBreadcrumbListon any page below the root
The deep catalog of schema types is in schema-markup-deep.
6. Ship the discoverability files
Three files belong at the site root for agents and crawlers:
/llms.txt: agent-facing index. See ship-llms-txt./ai.txt: AI training opt-out declaration. See ai-txt./{indexnow-key}.txt: IndexNow verification key. See indexnow.
The full discoverability-file checklist (including humans.txt, security.txt, manifest.json, OG defaults, favicon) lives at discoverability-files.
7. Wire the deploy pipeline
For GitHub Pages, see github-pages and deploy-quartz-site. For Cloudflare Pages, see set-up-quartz-with-cloudflare-pages. Either way the workflow should:
- Run the full build (
npx quartz build). - Fail on any lint, link, or JSON-LD validation error before deploy.
- Upload only the
public/directory as the deploy artifact.
8. Submit and verify post-deploy
After the first successful deploy:
# Sitemap reachable
curl -sI https://example.com/sitemap.xml | head -1
# JSON-LD parses
curl -s https://example.com/ | grep -A 20 'application/ld+json'
# llms.txt reachable
curl -sI https://example.com/llms.txt | head -1Submit the sitemap in GSC and Bing Webmaster Tools. Wait 24 hours, then check the Coverage report for indexed-vs-discovered counts.
Verify it worked
- Google Rich Results Test: paste any content URL; confirm structured data renders.
- GSC Coverage report: indexed count climbs over 7 days, no “Discovered, not indexed” spikes.
- Lighthouse SEO score: 95+ on every sampled page (
npx --yes @lhci/cli@latest collect). curl https://example.com/llms.txtreturns 200 with the index body.
Common errors
- Canonical points to
localhostor the staging URL.baseUrlis unset or wrong in build config. Fix and rebuild. - Sitemap returns 404 on the live site.
static/sitemap.xmlwas committed by hand, conflicts with the emitter, or the emitter is not enabled. Delete the static copy and re-runnpx quartz build. - JSON-LD shows as text in the body. The injection wrapped the JSON in
<pre>or escaped the angle brackets. Use a raw HTML block, not a markdown code fence. - GSC shows “Indexed, though blocked by robots.txt”. Over-broad
Disallow:line; usuallyDisallow: /left from a staging config. Loosen and resubmit.