Overview

A static site computes every SEO artifact at build time, so each one is deterministic and reviewable in a diff. The cost is that nothing is added later by a server: the build must emit every file a server-rendered site would. This guide wires a Quartz 4 build to do that; the steps carry over to other generators. For the order of a full launch, see launch-a-new-site.

Prerequisites

  • A build that emits to public/ (npx quartz build).
  • A production domain; see deploy-quartz-site.

Steps

1. Pin baseUrl

Quartz builds the sitemap, RSS feed, and absolute URLs from baseUrl in quartz.config.ts. Use the bare production domain.

configuration: { baseUrl: "yourdomain.com" }

2. Emit a self-referential canonical per page

Stock Quartz’s Head component does not write a canonical tag. Add one in a custom head component built from baseUrl plus the page slug, and register it as head in quartz.layout.ts.

npx quartz build
grep -L 'rel="canonical"' public/*.html   # lists pages missing the tag; expect none

3. Let the build write sitemap.xml

Plugin.ContentIndex({ enableSiteMap: true }) emits public/sitemap.xml. <lastmod> comes from CreatedModifiedDate, which reads frontmatter modified, lastmod, or updated, then git history, in the configured priority order. A custom key such as last_updated is not read.

xmllint --noout public/sitemap.xml && echo valid
grep -c "<url>" public/sitemap.xml

4. Place robots.txt in content/

Non-Markdown files in content/ are copied to the output root.

User-agent: *
Allow: /

Sitemap: https://yourdomain.com/sitemap.xml

Add Disallow: lines only for paths that must not be crawled; over-blocking is the common static-site bug.

5. Inject JSON-LD

Emit WebSite on the homepage, Article plus BreadcrumbList on content pages, from a head component. See add-jsonld-to-static-site.

6. Add the agent and IndexNow files

Ship /llms.txt (ship-llms-txt), optionally /ai.txt (ai-txt), and the IndexNow key file /{key}.txt (indexnow). The root-file checklist is discoverability-files.

7. Gate the deploy

The workflow should run the full build, fail on lint, link, or JSON-LD errors, and upload only public/. See deploy-quartz-site or deploy-a-static-site-to-cloudflare-pages.

Verify it worked

curl -sI https://yourdomain.com/sitemap.xml | head -1
curl -s https://yourdomain.com/ | grep -c 'application/ld+json'
curl -s https://yourdomain.com/ | grep -o 'rel="canonical"[^>]*'
curl -sI https://yourdomain.com/llms.txt | head -1

Then run the Rich Results Test on one content URL and submit the sitemap in Search Console; see submit-a-new-site-to-search-engines.

Common errors

  • Canonical points at localhost or a staging host. baseUrl is wrong; fix and rebuild.
  • /sitemap.xml returns 404. enableSiteMap is off, or a hand-committed copy conflicts with the emitter.
  • JSON-LD shows as text in the page. It was wrapped in <pre> or escaped; emit a raw script element, not a Markdown code fence.
  • Search Console reports “Indexed, though blocked by robots.txt”. A staging Disallow: / shipped to production.