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 build or equivalent) that emits to public/.
  • 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.html

If 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 count

Do 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:

  • WebSite on the home page (@type: "WebSite")
  • Article on every content page with headline, datePublished, dateModified, author
  • BreadcrumbList on 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 -1

Submit 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.txt returns 200 with the index body.

Common errors

  • Canonical points to localhost or the staging URL. baseUrl is unset or wrong in build config. Fix and rebuild.
  • Sitemap returns 404 on the live site. static/sitemap.xml was committed by hand, conflicts with the emitter, or the emitter is not enabled. Delete the static copy and re-run npx 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; usually Disallow: / left from a staging config. Loosen and resubmit.