---
title: Audit
description: blume audit crawls the built site and reports SEO and site-health issues — every finding names the source file and front matter line that fixes it.
---

[`blume validate`](/docs/cli/validate) reads your _content_; `blume audit` reads the _built site_. It crawls the HTML in `dist/` after a build and reports SEO and site-health issues — titles, meta descriptions, canonicals, Open Graph and X cards, headings, hreflang, images, the sitemap, `robots.txt`, and structured data.

Because Blume built the site, every finding names the source file **and the front matter line** that fixes it, not just the URL a crawler would see:

```
⚠ Meta description too long or too short   5 pages
    /docs/configuration/export    content/docs/configuration/export.mdx:3
    fix: Rewrite `description` in the frontmatter to fit the length range.
```

Run it after a build:

```bash
blume build
blume audit
```

Findings are grouped by check rather than listed per page, so the report reads as a to-do list. Use `--verbose` to expand every affected page with its full detail, and `--only`/`--skip` to work through one category at a time. `blume audit --list-checks` prints the full catalog, which is also the [check catalog](#check-catalog) at the end of this page — every finding's `docsUrl` links to its entry there.

## Flags

- `--fail-on error|warning|info` — the CI gate: exit non-zero at this severity or above. Defaults to `error`; `--strict` is an alias for `--fail-on warning`.
- `--url <origin>` — also probe a live deployment for status codes, response headers, and redirect chains.
- `--external` — probe outbound links over the network.
- `--only <check|category>` / `--skip <check|category>` — narrow the report to, or away from, these checks or categories (comma-separated). A term that names no check or category is an error that suggests the closest one, so a typo can't quietly empty the report.
- `--list-checks` — print every check the audit can report, then exit.
- `--verbose` — list every affected page with each finding's full detail, instead of the first few.
- `--json` — emit the report as JSON on stdout.
- `--claude` / `--codex` — hand the findings to Claude Code or Codex to fix interactively.

## Failing CI

The exit code is the contract. By default `blume audit` fails only on errors — things that are definitely broken, like a link to a page that was never built, a redirect loop, or an invalid sitemap. Advisory findings (a short description, a duplicate title) are warnings and do not fail the build:

```bash
blume audit                      # fails on errors
blume audit --fail-on warning    # also fails on warnings
```

## Checking a live deployment

Some things only the real server can tell you: whether a page that exists in `dist/` actually 404s behind a bad rewrite, whether responses are compressed, and whether an `X-Robots-Tag` header is quietly deindexing a page whose HTML looks perfectly fine. Point the audit at a deployment to add those checks:

```bash
blume audit --url https://docs.example.com
blume audit --url https://docs.example.com --external   # also probe outbound links
```

Outbound links are graded rather than flatly failed: a 404 is a broken link you can fix, while a 403 or 5xx is usually rate limiting or someone else's outage and is reported as a warning.

## Fixing the findings with an agent

If you use [Claude Code](https://claude.com/claude-code) or [Codex](https://developers.openai.com/codex/cli), the audit can hand its findings straight to it:

```bash
blume audit --claude   # or --codex
```

This writes the complete JSON report — every affected page, not the terminal's three-page preview — to a file and opens the agent interactively with a prompt that walks it through the findings: edit the source file each finding names, apply its suggested fix, then run `blume build` and `blume audit` again until the report is clean. The session is interactive by design: you review the edits through the agent's own permission flow, and the agent is told never to fix a finding by deleting content.

`--only` and `--skip` narrow the handoff the same way they narrow the report, so you can send one category at a time.

## What it does and doesn't check

The check set is deliberately narrower than a general-purpose SEO crawler's. Much of what such a crawler reports cannot happen to a Blume site — it never emits `rel=nofollow`, and Vite's content-hashed bundles are never missing or redirecting — and reporting those as permanent zeroes would just teach you to ignore the report.

Two limits worth stating plainly:

- **Structured data** is validated for well-formedness (valid JSON, a `@context`, a `@type` on every node). Blume does not validate against the full schema.org vocabulary or Google's rich-results rules.
- **Core Web Vitals** are not checked. They need a real browser, and a flag that quietly measured nothing would be worse than not having one — so `blume audit` reports the layout-shift causes it _can_ see offline (images with no `width`/`height`, oversized assets) and leaves the rest alone for now.

Anything the audit did not run is reported as skipped rather than silently passing:

```
⊘ network      skipped — pass --url <origin> (11 checks)
⊘ external     skipped — pass --external (2 checks)
```

## Check catalog

Every check `blume audit` can report, grouped by category, with its default severity, the tier it runs in, and the fix the report suggests. `--only` and `--skip` accept these ids (without the `BLUME_AUDIT_` prefix is fine too) or the category names.

### Content

#### Title tag missing or empty [#title-missing]

`BLUME_AUDIT_TITLE_MISSING` · error · from the built HTML

Fix: Add a `title` to the page's frontmatter.

#### Multiple title tags [#title-multiple]

`BLUME_AUDIT_TITLE_MULTIPLE` · error · from the built HTML

Fix: Remove the extra `<title>` from the page's layout or MDX.

#### Title too long or too short [#title-length]

`BLUME_AUDIT_TITLE_LENGTH` · warning · from the built HTML

Fix: Rewrite `title` in the frontmatter to fit the length range.

#### Meta description missing or empty [#description-missing]

`BLUME_AUDIT_DESCRIPTION_MISSING` · warning · from the built HTML

Fix: Add a `description` to the page's frontmatter.

#### Multiple meta description tags [#description-multiple]

`BLUME_AUDIT_DESCRIPTION_MULTIPLE` · error · from the built HTML

Fix: Remove the extra description `<meta>` from the page's layout or MDX.

#### Meta description too long or too short [#description-length]

`BLUME_AUDIT_DESCRIPTION_LENGTH` · warning · from the built HTML

Fix: Rewrite `description` in the frontmatter to fit the length range.

#### H1 tag missing or empty [#h1-missing]

`BLUME_AUDIT_H1_MISSING` · warning · from the built HTML

Fix: Give the page a `title` — Blume renders it as the page's `<h1>`.

#### Multiple H1 tags [#h1-multiple]

`BLUME_AUDIT_H1_MULTIPLE` · warning · from the built HTML

Fix: Demote the extra `# Heading` in the body to `##` — Blume already renders `title` as the h1.

#### Low word count [#low-word-count]

`BLUME_AUDIT_LOW_WORD_COUNT` · info · from the built HTML

Fix: Expand the page, or fold it into a larger one.

#### Heading levels skip (e.g. h2 to h4) [#heading-skip]

`BLUME_AUDIT_HEADING_SKIP` · info · from the built HTML

Fix: Adjust the heading to the next level down — skipped levels break table-of-contents nesting and screen-reader outlines.

#### Page is dated in the future [#future-dated-page]

`BLUME_AUDIT_FUTURE_DATED_PAGE` · info · from the built HTML

Fix: Correct the `date`, or hold the page back until it is meant to be live.

#### Viewport not set [#viewport-missing]

`BLUME_AUDIT_VIEWPORT_MISSING` · error · from the built HTML

Fix: Restore the viewport `<meta>` in your ejected layout.

### Duplicates

#### Duplicate title [#duplicate-title]

`BLUME_AUDIT_DUPLICATE_TITLE` · warning · from the built HTML

Fix: Give each page a distinct `title` — search engines show it as the result headline.

#### Duplicate meta description [#duplicate-description]

`BLUME_AUDIT_DUPLICATE_DESCRIPTION` · warning · from the built HTML

Fix: Give each page a distinct `description`.

#### Duplicate pages without canonical [#duplicate-content]

`BLUME_AUDIT_DUPLICATE_CONTENT` · warning · from the built HTML

Fix: Merge the pages, or set `seo.canonical` on all but one.

### Indexability

#### deployment.site is not set [#site-not-set]

`BLUME_AUDIT_SITE_NOT_SET` · warning · from the built HTML

Fix: Set `deployment.site` in blume.config.ts to the site's public URL.

#### deployment.site is inferred at deploy time [#site-inferred-at-deploy]

`BLUME_AUDIT_SITE_INFERRED_AT_DEPLOY` · info · from the built HTML

Fix: Audit a production-like build (e.g. `VERCEL=1 VERCEL_PROJECT_PRODUCTION_URL=<host> blume build`) or the deployment itself with `--url <origin>`. Do not hardcode `deployment.site` — the platform sets it on every deploy.

#### Page is noindex but declares a canonical [#canonical-on-noindex]

`BLUME_AUDIT_CANONICAL_ON_NOINDEX` · warning · from the built HTML

Fix: Drop the canonical from noindex pages — Google treats the pairing as contradictory and may ignore one of the two.

#### Draft page is in the build [#draft-page-published]

`BLUME_AUDIT_DRAFT_PAGE_PUBLISHED` · warning · from the built HTML

Fix: Rebuild without `--preview` before deploying, or remove `draft: true` if the page is ready to ship.

#### Canonical URL missing [#canonical-missing]

`BLUME_AUDIT_CANONICAL_MISSING` · warning · from the built HTML

Fix: Set `deployment.site` so Blume can emit absolute canonical URLs.

#### Non-canonical page [#canonical-not-self]

`BLUME_AUDIT_CANONICAL_NOT_SELF` · info · from the built HTML

Fix: Point `seo.canonical` at this page, or remove it to use the default self-canonical.

#### Canonical points to a broken or redirecting page [#canonical-bad-target]

`BLUME_AUDIT_CANONICAL_BAD_TARGET` · error · from the built HTML

Fix: Point `seo.canonical` at a page that exists and doesn't redirect.

#### Canonical protocol does not match the site [#canonical-protocol-mismatch]

`BLUME_AUDIT_CANONICAL_PROTOCOL_MISMATCH` · error · from the built HTML

Fix: Use the same protocol in `seo.canonical` as in `deployment.site`.

#### Page is not indexable [#robots-meta-unexpected]

`BLUME_AUDIT_ROBOTS_META_UNEXPECTED` · info · from the built HTML

Fix: Remove `noindex` from the page's frontmatter if it should be indexed.

#### Page exceeds Googlebot's 2 MB crawl limit [#html-too-large]

`BLUME_AUDIT_HTML_TOO_LARGE` · error · from the built HTML

Fix: Split the page — Googlebot stops reading an HTML document at 2 MB.

#### X-Robots-Tag header conflicts with the page's robots meta [#robots-header-conflict]

`BLUME_AUDIT_ROBOTS_HEADER_CONFLICT` · error · needs `--url`

Fix: Remove the X-Robots-Tag header, or align it with the page's robots meta.

### Links

#### Page has links to a broken page [#link-to-broken]

`BLUME_AUDIT_LINK_TO_BROKEN` · error · from the built HTML

Fix: Fix the link target, or create the page it points at.

#### Page has links to a redirect [#link-to-redirect]

`BLUME_AUDIT_LINK_TO_REDIRECT` · warning · from the built HTML

Fix: Link straight to the destination instead of through the redirect.

#### Orphan page (only reachable from navigation) [#orphan-page]

`BLUME_AUDIT_ORPHAN_PAGE` · warning · from the built HTML

Fix: Link to this page from the body of a related page.

#### Internal link hardcodes the site's own origin [#internal-link-absolute]

`BLUME_AUDIT_INTERNAL_LINK_ABSOLUTE` · warning · from the built HTML

Fix: Use a root-relative path (`/docs/x`) so the link survives previews and `basePath`.

#### Page has nofollow outgoing internal links [#internal-link-nofollow]

`BLUME_AUDIT_INTERNAL_LINK_NOFOLLOW` · info · from the built HTML

Fix: Drop `rel="nofollow"` — it stops this page passing authority internally.

#### Double slash in URL [#double-slash-url]

`BLUME_AUDIT_DOUBLE_SLASH_URL` · error · from the built HTML

Fix: Check `basePath` / `deployment.base` — a trailing slash there produces `//`.

#### Link fragment matches no id on the target page [#anchor-broken]

`BLUME_AUDIT_ANCHOR_BROKEN` · warning · from the built HTML

Fix: Point the fragment at a heading that exists on the target page, or fix the heading it meant.

#### URL contains uppercase, underscores, or spaces [#url-style]

`BLUME_AUDIT_URL_STYLE` · info · from the built HTML

Fix: Rename the source file to a lowercase, hyphenated slug — and add a redirect from the old URL if it was already published.

### Redirects

#### Broken redirect [#redirect-broken]

`BLUME_AUDIT_REDIRECT_BROKEN` · error · from the built HTML

Fix: Point the redirect at a page that exists.

#### Redirect loop [#redirect-loop]

`BLUME_AUDIT_REDIRECT_LOOP` · error · from the built HTML

Fix: Break the cycle in `redirects` — it never resolves.

#### Redirect chain [#redirect-chain]

`BLUME_AUDIT_REDIRECT_CHAIN` · warning · from the built HTML

Fix: Point every hop straight at the final destination.

#### Meta refresh redirect [#meta-refresh]

`BLUME_AUDIT_META_REFRESH` · warning · from the built HTML

Fix: Use a real redirect in `blume.config.ts` instead of a meta refresh.

#### Redirect source is also a real page [#redirect-source-is-page]

`BLUME_AUDIT_REDIRECT_SOURCE_IS_PAGE` · error · from the built HTML

Fix: Remove the redirect, or delete the page it shadows — the page wins and the redirect never fires.

#### HTTPS to HTTP redirect [#redirect-to-http]

`BLUME_AUDIT_REDIRECT_TO_HTTP` · error · needs `--url`

Fix: Redirect to the HTTPS URL.

### Social cards

#### Open Graph tags missing or incomplete [#og-incomplete]

`BLUME_AUDIT_OG_INCOMPLETE` · warning · from the built HTML

Fix: Add a `description` — Blume fills the rest of the Open Graph tags for you.

#### Open Graph image missing [#og-image-missing]

`BLUME_AUDIT_OG_IMAGE_MISSING` · warning · from the built HTML

Fix: Set `deployment.site` to turn on generated OG images, or set `seo.image` on the page.

#### Open Graph image is not in the build [#og-image-broken]

`BLUME_AUDIT_OG_IMAGE_BROKEN` · warning · from the built HTML

Fix: Point `seo.image` at a file that exists, or rebuild — a dead og:image renders as a blank card everywhere the page is shared.

#### Open Graph image is too small for large cards [#og-image-small]

`BLUME_AUDIT_OG_IMAGE_SMALL` · warning · from the built HTML

Fix: Use an image of at least 1200×630 — smaller ones render blurry or get cropped into small-card layouts.

#### Open Graph URL not matching canonical [#og-url-mismatch]

`BLUME_AUDIT_OG_URL_MISMATCH` · warning · from the built HTML

Fix: Align `og:url` with the page's canonical URL.

#### X (Twitter) card missing or incomplete [#twitter-card-incomplete]

`BLUME_AUDIT_TWITTER_CARD_INCOMPLETE` · warning · from the built HTML

Fix: Set `seo.x.handle` in blume.config.ts so X can attribute the card.

### Internationalization

#### HTML lang attribute missing [#html-lang-missing]

`BLUME_AUDIT_HTML_LANG_MISSING` · error · from the built HTML

Fix: Restore the `lang` attribute on `<html>` in your ejected layout.

#### HTML lang attribute invalid [#html-lang-invalid]

`BLUME_AUDIT_HTML_LANG_INVALID` · error · from the built HTML

Fix: Use a valid BCP 47 tag (e.g. `en`, `en-GB`) for the locale.

#### Hreflang and HTML lang mismatch [#hreflang-lang-mismatch]

`BLUME_AUDIT_HREFLANG_LANG_MISMATCH` · error · from the built HTML

Fix: The page's `<html lang>` must match its own hreflang annotation.

#### Hreflang annotation invalid [#hreflang-invalid]

`BLUME_AUDIT_HREFLANG_INVALID` · error · from the built HTML

Fix: Use a valid BCP 47 tag in the hreflang annotation.

#### Self-reference hreflang annotation missing [#hreflang-self-missing]

`BLUME_AUDIT_HREFLANG_SELF_MISSING` · warning · from the built HTML

Fix: A page's hreflang set must include a self-reference.

#### X-default hreflang annotation missing [#hreflang-xdefault-missing]

`BLUME_AUDIT_HREFLANG_XDEFAULT_MISSING` · info · from the built HTML

Fix: Add an `x-default` alternate pointing at the default-locale page.

#### Missing reciprocal hreflang (no return-tag) [#hreflang-no-return-tag]

`BLUME_AUDIT_HREFLANG_NO_RETURN_TAG` · error · from the built HTML

Fix: Every page in an hreflang group must link back to every other one.

#### Hreflang points to a broken, redirecting, or non-canonical page [#hreflang-bad-target]

`BLUME_AUDIT_HREFLANG_BAD_TARGET` · error · from the built HTML

Fix: Point the hreflang alternate at a page that exists and is canonical.

#### Hreflang group has a language conflict [#hreflang-conflict]

`BLUME_AUDIT_HREFLANG_CONFLICT` · error · from the built HTML

Fix: Each language in an hreflang group must name exactly one page.

### Assets

#### Missing alt text [#image-alt-missing]

`BLUME_AUDIT_IMAGE_ALT_MISSING` · warning · from the built HTML

Fix: Add descriptive `alt` text, or `alt=""` if the image is decorative.

#### Image broken [#image-broken]

`BLUME_AUDIT_IMAGE_BROKEN` · error · from the built HTML

Fix: Fix the image path, or add the file to public/.

#### Asset file size too large [#asset-too-large]

`BLUME_AUDIT_ASSET_TOO_LARGE` · warning · from the built HTML

Fix: Compress the asset, or serve a modern format (WebP/AVIF).

#### Image has no width/height [#image-missing-dimensions]

`BLUME_AUDIT_IMAGE_MISSING_DIMENSIONS` · warning · from the built HTML

Fix: Set `width` and `height` so the browser can reserve space (avoids layout shift).

#### Referenced script, style, or asset is missing from the build [#subresource-missing]

`BLUME_AUDIT_SUBRESOURCE_MISSING` · error · from the built HTML

Fix: Fix the reference, or restore the missing file.

#### HTTPS/HTTP mixed content [#mixed-content]

`BLUME_AUDIT_MIXED_CONTENT` · error · from the built HTML

Fix: Load the subresource over HTTPS — browsers block mixed content.

### Sitemap

#### Indexable page not in sitemap [#indexable-page-not-in-sitemap]

`BLUME_AUDIT_INDEXABLE_PAGE_NOT_IN_SITEMAP` · warning · from the built HTML

Fix: Remove `draft`/`hidden`/`noindex` from the page's frontmatter if it should be indexed.

#### Noindex page in sitemap [#noindex-in-sitemap]

`BLUME_AUDIT_NOINDEX_IN_SITEMAP` · error · from the built HTML

Fix: A noindex page should not be advertised in the sitemap.

#### Non-canonical page in sitemap [#non-canonical-in-sitemap]

`BLUME_AUDIT_NON_CANONICAL_IN_SITEMAP` · error · from the built HTML

Fix: List only canonical URLs in the sitemap.

#### Sitemap names a page that does not exist or redirects [#sitemap-bad-url]

`BLUME_AUDIT_SITEMAP_BAD_URL` · error · from the built HTML

Fix: Remove the URL from the sitemap, or build the page it names.

#### Sitemap has a syntax error or wrong format [#sitemap-invalid]

`BLUME_AUDIT_SITEMAP_INVALID` · error · from the built HTML

Fix: Sitemaps must be valid XML in the sitemaps.org urlset format.

#### Sitemap exceeds 50 MB or 50,000 URLs [#sitemap-too-large]

`BLUME_AUDIT_SITEMAP_TOO_LARGE` · error · from the built HTML

Fix: Split the sitemap — the limits are 50 MB and 50,000 URLs.

#### Sitemap lastmod is invalid or in the future [#sitemap-lastmod-invalid]

`BLUME_AUDIT_SITEMAP_LASTMOD_INVALID` · warning · from the built HTML

Fix: Use a real W3C date that is not in the future — search engines that catch a sitemap lying about freshness stop trusting its lastmod entirely.

#### Sitemap includes URLs out of its scope [#sitemap-out-of-scope]

`BLUME_AUDIT_SITEMAP_OUT_OF_SCOPE` · warning · from the built HTML

Fix: A sitemap may only list URLs on its own origin.

#### Sitemap is not accessible [#sitemap-not-accessible]

`BLUME_AUDIT_SITEMAP_NOT_ACCESSIBLE` · error · needs `--url`

Fix: Make sitemap.xml reachable at the site root.

### robots.txt

#### robots.txt missing [#robots-missing]

`BLUME_AUDIT_ROBOTS_MISSING` · warning · from the built HTML

Fix: Set `seo.robots: true` to generate robots.txt.

#### robots.txt has a syntax error [#robots-invalid]

`BLUME_AUDIT_ROBOTS_INVALID` · error · from the built HTML

Fix: Every robots.txt line must be a `Field: value` directive or a comment.

#### robots.txt disallows a page that is in the sitemap [#robots-disallows-indexable]

`BLUME_AUDIT_ROBOTS_DISALLOWS_INDEXABLE` · error · from the built HTML

Fix: A page can't be both disallowed in robots.txt and advertised in the sitemap.

#### robots.txt does not reference the sitemap [#robots-sitemap-missing]

`BLUME_AUDIT_ROBOTS_SITEMAP_MISSING` · info · from the built HTML

Fix: Set `deployment.site` so robots.txt can reference the sitemap.

#### robots.txt is not accessible [#robots-not-accessible]

`BLUME_AUDIT_ROBOTS_NOT_ACCESSIBLE` · error · needs `--url`

Fix: Make robots.txt reachable at the site root.

### AI discoverability

#### llms.txt missing from the build [#llms-txt-missing]

`BLUME_AUDIT_LLMS_TXT_MISSING` · warning · from the built HTML

Fix: Rebuild — `agents.llmsTxt` is enabled but the build has no llms.txt. If that's intentional, set `agents.llmsTxt: false`.

#### llms.txt lists a page the build does not serve [#llms-txt-stale-entry]

`BLUME_AUDIT_LLMS_TXT_STALE_ENTRY` · warning · from the built HTML

Fix: Rebuild so llms.txt matches the site — a stale entry sends an AI agent to a page that is not there.

#### Indexable page missing from llms.txt [#llms-txt-page-missing]

`BLUME_AUDIT_LLMS_TXT_PAGE_MISSING` · warning · from the built HTML

Fix: Rebuild so llms.txt matches the site; if the page is deliberately excluded, mark it `seo.noindex` or `sidebar.hidden`.

#### No DNS-AID agent-discovery records [#dns-aid-missing]

`BLUME_AUDIT_DNS_AID_MISSING` · info · needs `--url`

Fix: Publish a ServiceMode SVCB or HTTPS record at `_index._agents.<host>` with your DNS provider so agents can discover the site via DNS (DNS-AID).

#### DNS-AID records are not DNSSEC-authenticated [#dns-aid-unsigned]

`BLUME_AUDIT_DNS_AID_UNSIGNED` · info · needs `--url`

Fix: Enable DNSSEC for the zone so validating resolvers return authenticated DNS-AID answers; if your DNS provider doesn't support DNSSEC, the records still work unsigned.

### Structured data

#### Structured data is not valid JSON [#jsonld-invalid]

`BLUME_AUDIT_JSONLD_INVALID` · error · from the built HTML

Fix: The JSON-LD block must be valid JSON.

#### Structured data is missing required properties [#jsonld-incomplete]

`BLUME_AUDIT_JSONLD_INCOMPLETE` · warning · from the built HTML

Fix: Every JSON-LD node needs `@context` and `@type`.

### Live deployment

#### 4XX page [#http-4xx]

`BLUME_AUDIT_HTTP_4XX` · error · needs `--url`

Fix: The page is linked or in the sitemap but the deployment 404s it.

#### 5XX page [#http-5xx]

`BLUME_AUDIT_HTTP_5XX` · error · needs `--url`

Fix: The deployment is erroring on this page.

#### Timed out [#http-timeout]

`BLUME_AUDIT_HTTP_TIMEOUT` · error · needs `--url`

Fix: The page did not respond in time.

#### Not compressed [#not-compressed]

`BLUME_AUDIT_NOT_COMPRESSED` · warning · needs `--url`

Fix: Enable gzip or brotli on the host.

#### Slow page [#slow-response]

`BLUME_AUDIT_SLOW_RESPONSE` · warning · needs `--url`

Fix: The page was slow to respond.

#### External link is broken [#external-link-broken]

`BLUME_AUDIT_EXTERNAL_LINK_BROKEN` · error · needs `--external`

Fix: Fix or remove the outbound link.

#### External link redirects [#external-link-redirect]

`BLUME_AUDIT_EXTERNAL_LINK_REDIRECT` · info · needs `--external`

Fix: Link straight to the destination.
