# SEO and the published site

> Where an author edits search and social metadata, the four machine-readable files the build emits, and the one rule the draft/preview split turns on.

Source: https://editor.astrocraftthemes.com/docs/seo/

The CMS does not own your site's `<head>` — your own layout does. What it owns is the surface an author edits, and the guarantee that the CMS's own routes never leak into anything a crawler reads.

## Where an author edits it

Two places, one panel, one declaration shape.

**In the entry editor**, the SEO tab renders the five roles your collection named in `src/admin.config.ts` — search title, search description, social image, and the two social overrides. See [Details, SEO and History](/docs/entry-fields/) for how the search preview and the override model behave.

**In the page composer**, the same panel renders over a **layout's props**, using the same five key names. A layout declares them in `components.overrides`, and it has to be a declaration rather than a derivation because the shipped layout spells its picture prop `image` and yours will not. See [The component library](/docs/component-library/).

## The four machine-readable files

Emitted by your site's build, not by the CMS:

| File                | How it is produced                                                           |
| :------------------ | :--------------------------------------------------------------------------- |
| `sitemap-index.xml` | the sitemap integration                                                      |
| `robots.txt`        | a prerendered endpoint                                                       |
| `rss.xml`           | a prerendered endpoint                                                       |
| `llms.txt`          | a prerendered endpoint, an editorial content map rather than an auto-sitemap |

All four read **one statement** of what "published" means. That rule lives in a single module with four callers — the list pages, the entry pages, RSS and `llms.txt` — so they cannot disagree about whether a draft is public.

`robots.txt` derives its policy from the `PREVIEW` flag: the staging deploy serves `Disallow: /`, and production allows everything but `/admin/`, naming the sitemap.

Settings → General SEO shows all four URLs as readouts, computed from your committed host. There is nothing to enable there, because these regenerate on every build.

> **The sitemap needs a filter for `/admin/`.** The sitemap integration enumerates non-dynamic on-demand routes too, so without an explicit filter every admin screen lands in your sitemap, sign-in included. "Excluded by construction" was measured and found false. The shipped `astro.config.mjs` has the filter; if you wrote your own, add it.

## The draft rule and the two builds

One flag: `PREVIEW=1`.

Your **staging** branch builds with it and renders drafts. Your **production** branch builds without it and never does. That is the whole preview mechanism, and it is why publishing is a fast-forward of a branch rather than a deploy API call — the branch your production build watches is the thing that changed.

An entry marked `draft: true` still **blocks a publish** if its frontmatter fails your schema. It rides the branch even though nothing renders it.

## The build stamp

Every page stamps the commit it was built from:

```astro
<meta name="build-rev" content={import.meta.env.BUILD_REV} />
```

After a publish, the review screen polls your production site until that stamp is a descendant of the commit it pushed. Keep the line in whatever page answers `/`; without it, publishing works and you simply never get told it landed.

The stamp is read **server-side**, because your site is usually another origin and CORS bars a browser from reading it.

## The CMS is not indexed

Every admin route renders on demand and is behind the session guard, `robots.txt` disallows the tree, and the sitemap filter keeps it out. Three independent mechanisms, which is the right number for something whose failure mode is a sign-in page in Google.

## Troubleshooting

**Admin routes appear in my sitemap.** You are missing the `/admin/` filter in the sitemap integration's config.

**Drafts show on production.** That build has `PREVIEW=1` set. It should only ever be on the staging deploy.

**Drafts do not show on staging.** That build is missing `PREVIEW=1`.

**A social image does not render on a shared link.** Crawlers run no JavaScript and often will not follow a redirect, so the image must be a plain file at a stable absolute URL. That is why the shipped schema types social images as `z.string()` rather than Astro's `image()`.

**`og:` tags do not match my search title.** Expected, if the social overrides are set. Empty means "same as search"; a value means an override. The JSON-LD headline always stays your real title.