Audit
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 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:
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 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 toerror;--strictis an alias for--fail-on warning.--url <origin>— also probe a live deployment for status codes, response headers, and configured redirects.--external— probe outbound links over the network.--ignore <glob>— with--external, don’t probe an outbound link whose URL matches the glob, like--ignore "https://api.acme.example/**". Repeat the flag for more patterns. It matches the wayblume validate --ignoredoes, and internal links are always checked.--only <check|category>/--skip <check|category>— narrow the report to, or away from, these checks or categories (comma-separated). The audit count in the summary counts only the checks left in. 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.--codex/--claude— hand the findings to Codex or Claude Code 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:
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, whether an X-Robots-Tag header is quietly deindexing a page whose HTML looks perfectly fine, and whether each old URL in redirects really redirects where you configured it. Point the audit at a deployment to add those checks:
blume audit --url https://docs.example.com
blume audit --url https://docs.example.com --external # also probe outbound links
These requests go out as a plain HTTP client, not as a crawler: the audit doesn’t send Googlebot’s or an AI crawler’s user agent, since a host’s bot protection can block a client pretending to be one. A firewall or CDN rule that treats crawlers differently isn’t exercised, so check it with the crawler’s own tools, such as Google Search Console’s URL Inspection.
The audit requests every exact redirect’s old URL (a pattern names no single URL to request). One that 404s is an error. One that answers 200 is a warning: a host that doesn’t read the redirect file the build wrote serves the build’s meta-refresh page there instead of an HTTP redirect, which readers follow but crawlers may not. See Redirects for what each host reads.
Outbound links are graded rather than flatly failed: a 404 or 410, or a host name that doesn’t resolve, is a broken link you can fix, while any other error status (a 401 or 403 from a site that turns bots away, a 429, a 5xx), a refused connection, or a timeout is usually rate limiting or someone else’s outage and is reported as a warning. Leave out URLs the audit can’t check from where it runs, like a placeholder domain or a local server, with --ignore.
Fixing the findings with an agent
If you use Codex or Claude Code, the audit can hand its findings straight to it:
blume audit --codex # or --claude
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.
Limits worth stating plainly:
- Structured data is validated for well-formedness (valid JSON, a
@context, a@typeon every node). Blume does not validate against the full schema.org vocabulary or Google’s rich-results rules. - Contrast is checked for the colors your theme config sets, not element by element. The audit doesn’t render pages, so a color you write by hand in MDX or
theme.cssisn’t measured. - 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 auditreports the layout-shift causes it can see offline (images with nowidth/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> (12 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 a category’s key. The key isn’t always the category’s heading, so use this table: blume audit --only i18n, not --only Internationalization.
| Category | Key |
|---|---|
| Accessibility | accessibility |
| Content | content |
| Duplicates | duplicates |
| Indexability | indexability |
| Links | links |
| Redirects | redirects |
| Social cards | social |
| Internationalization | i18n |
| Assets | assets |
| Sitemap | sitemap |
| robots.txt | robots |
| AI discoverability | ai |
| Structured data | structured-data |
| Live deployment | network |
Content
Title tag missing or empty
BLUME_AUDIT_TITLE_MISSING · error · from the built HTML
Fix: Add a title to the page’s frontmatter.
Multiple title tags
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
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
BLUME_AUDIT_DESCRIPTION_MISSING · warning · from the built HTML
Fix: Add a description to the page’s frontmatter.
Multiple meta description tags
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
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
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
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
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)
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
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
BLUME_AUDIT_VIEWPORT_MISSING · error · from the built HTML
Fix: Restore the viewport <meta> in your ejected layout.
Duplicates
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
BLUME_AUDIT_DUPLICATE_DESCRIPTION · warning · from the built HTML
Fix: Give each page a distinct description.
Duplicate pages without canonical
BLUME_AUDIT_DUPLICATE_CONTENT · warning · from the built HTML
Fix: Merge the pages, or point seo.canonical on all but one at the page to index.
Indexability
deployment.site is 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
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
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
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
BLUME_AUDIT_CANONICAL_MISSING · warning · from the built HTML
Fix: Set deployment.site so Blume can emit absolute canonical URLs.
Non-canonical page
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
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
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
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
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
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
BLUME_AUDIT_LINK_TO_BROKEN · error · from the built HTML
Fix: Fix the link target, or create the page it points at.
Under deployment.base, a root-relative link without the base is reported here, since the host serves nothing of this build there. A full URL on the same host outside the base isn’t: that’s how a page links to another app the host serves beside the docs.
Page has links to a 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)
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
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
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
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
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
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
BLUME_AUDIT_REDIRECT_BROKEN · error · from the built HTML
Fix: Point the redirect at a page that exists.
Redirect loop
BLUME_AUDIT_REDIRECT_LOOP · error · from the built HTML
Fix: Break the cycle in redirects — it never resolves.
Redirect chain
BLUME_AUDIT_REDIRECT_CHAIN · warning · from the built HTML
Fix: Point every hop straight at the final destination.
Meta refresh redirect
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
BLUME_AUDIT_REDIRECT_SOURCE_IS_PAGE · error · from the built HTML
Fix: Remove the redirect, or delete the page it shadows — a path can’t be both, and which one readers get depends on the host.
HTTPS to HTTP redirect
BLUME_AUDIT_REDIRECT_TO_HTTP · error · needs --url
Fix: Redirect to the HTTPS URL.
Live site doesn’t redirect as configured
BLUME_AUDIT_REDIRECT_NOT_SERVED · error · needs --url
Fix: Deploy the redirect file your host reads (see Redirects in the deployment docs), or name the host in deployment.
A warning instead when the old URL answers 200 (the meta-refresh page) or redirects somewhere other than the configured destination.
Social cards
Open Graph tags missing or 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
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
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
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
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
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
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
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
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
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
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
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)
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
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
BLUME_AUDIT_HREFLANG_CONFLICT · error · from the built HTML
Fix: Each language in an hreflang group must name exactly one page.
Accessibility
These run on your theme config, not on each page: a theme color is one choice for the whole site. The check resolves colors the way the build does (a preset’s shade for each mode, and white or dark labels on fills) and measures every text color the theme draws against the WCAG AA ratio for body text, 4.5:1, in light and dark mode.
Theme colors miss WCAG AA contrast
BLUME_AUDIT_THEME_CONTRAST_LOW · warning · from the theme config
Fix: Pick a darker shade for light mode or a lighter one for dark mode, per mode with theme.accent: { light, dark }.
It checks accent text on the page, labels on accent and action fills, and body and secondary text on a custom background. Each finding names the pair, the mode, and its ratio.
Theme color can’t be checked for contrast
BLUME_AUDIT_THEME_COLOR_UNCHECKED · info · from the theme config
Fix: Write the color as hex, rgb(), hsl(), hwb(), lab(), lch(), oklab(), oklch(), or a named color so its contrast can be checked.
Assets
Missing alt text
BLUME_AUDIT_IMAGE_ALT_MISSING · warning · from the built HTML
Fix: Add descriptive alt text, or alt="" if the image is decorative.
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
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
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
BLUME_AUDIT_SUBRESOURCE_MISSING · error · from the built HTML
Fix: Fix the reference, or restore the missing file.
HTTPS/HTTP 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
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. A public/sitemap.xml replaces the generated sitemap, so add the page there or delete that file.
Noindex page 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
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
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
BLUME_AUDIT_SITEMAP_INVALID · error · from the built HTML
Fix: Sitemaps must be valid XML in the sitemaps.org urlset or sitemap index format.
Sitemap exceeds 50 MB or 50,000 URLs
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
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
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
BLUME_AUDIT_SITEMAP_NOT_ACCESSIBLE · error · needs --url
Fix: Make sitemap.xml reachable at the site root.
robots.txt
robots.txt 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
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
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 blocks one crawler from pages in the sitemap
BLUME_AUDIT_ROBOTS_BLOCKS_CRAWLER · warning · from the built HTML
Fix: Narrow or remove the rule in that crawler’s User-agent group if it should read these pages. Blocking an AI crawler on purpose is fine; skip this check.
A crawler named in its own User-agent group follows only that group, so a rule there (User-agent: GPTBot then Disallow: /) shuts it out while every other crawler reads the page. The audit reports each crawler once, at the rule that blocks its first page.
robots.txt does not reference the sitemap
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
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
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
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
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, set ai.exclude: true in its front matter.
No DNS-AID agent-discovery records
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
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
BLUME_AUDIT_JSONLD_INVALID · error · from the built HTML
Fix: The JSON-LD block must be valid JSON.
Structured data is missing required properties
BLUME_AUDIT_JSONLD_INCOMPLETE · warning · from the built HTML
Fix: Every JSON-LD node needs @context and @type.
Live deployment
4XX page
BLUME_AUDIT_HTTP_4XX · error · needs --url
Fix: The page is linked or in the sitemap but the deployment 404s it.
5XX page
BLUME_AUDIT_HTTP_5XX · error · needs --url
Fix: The deployment is erroring on this page.
Timed out
BLUME_AUDIT_HTTP_TIMEOUT · error · needs --url
Fix: The page did not respond in time.
Not compressed
BLUME_AUDIT_NOT_COMPRESSED · warning · needs --url
Fix: Enable gzip or brotli on the host.
Slow page
BLUME_AUDIT_SLOW_RESPONSE · warning · needs --url
Fix: The page was slow to respond.
External link is broken
BLUME_AUDIT_EXTERNAL_LINK_BROKEN · error · needs --external
Fix: Fix or remove the outbound link.
External link redirects
BLUME_AUDIT_EXTERNAL_LINK_REDIRECT · info · needs --external
Fix: Link straight to the destination.