---
title: "How to ship static-site SEO end-to-end"
slug: "static-site-seo"
category: "howto"
tags: ["howto", "seo", "ssg", "quartz", "build-time", "json-ld", "sitemap"]
status: "stable"
last_updated: 2026-05-15
summary: "Ship every SEO artifact a static site needs at build time: canonicals, sitemap, robots, JSON-LD, OG images, llms.txt, and verify with curl before deploy."
related: ["[[seo/technical]]", "[[seo/structured-data]]", "[[seo/discoverability-files]]", "[[howto/add-jsonld-to-static-site]]", "[[howto/deploy-quartz-site]]", "[[howto/ship-llms-txt]]", "[[ops/github-pages]]", "[[tooling/quartz-config]]", "[[seo/glossary]]", "[[seo/audit-checklist]]"]
---

## 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 [[seo/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`:

```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 [[seo/technical]]. See [[tooling/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:

```bash
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:

```bash
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 [[howto/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 [[seo/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 [[howto/ship-llms-txt]].
- `/ai.txt`: AI training opt-out declaration. See [[seo/ai-txt]].
- `/{indexnow-key}.txt`: IndexNow verification key. See [[seo/indexnow]].

The full discoverability-file checklist (including `humans.txt`, `security.txt`, `manifest.json`, OG defaults, favicon) lives at [[seo/discoverability-files]].

### 7. Wire the deploy pipeline

For GitHub Pages, see [[ops/github-pages]] and [[howto/deploy-quartz-site]]. For Cloudflare Pages, see [[howto/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:

```bash
# 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.

## Related

- [[seo/technical]]
- [[seo/structured-data]]
- [[seo/discoverability-files]]
- [[howto/add-jsonld-to-static-site]]
- [[howto/deploy-quartz-site]]
- [[howto/ship-llms-txt]]
- [[ops/github-pages]]
- [[tooling/quartz-config]]
- [[seo/glossary]]
- [[seo/audit-checklist]]
