SEO Audit
One-click multi-category audit — SEO, Performance, GEO/AIO, CMS health — covering every URL in your sitemap.
Every Brandfine workspace gets a built-in SEO audit alongside its analytics. One click runs ~35 checks across four categories, samples your sitemap for lab + field performance via Google PageSpeed Insights, parses each post's rendered HTML, and surfaces a grouped list of issues with fix suggestions.
The audit is designed to be useful for any site shape — whether
your posts live in Brandfine, in static markdown files, or aren't
posts at all (landing-page-only sites). URL discovery comes from
your /sitemap.xml, not from assumptions about your URL structure.
What you see in the CMS
Sidebar → Growth → SEO & Analytics → click any workspace → SEO audit tab. The page has:
- Four score cards (SEO / Performance / GEO/AIO / CMS health) — 0-100, color-coded green/amber/rose.
- Coverage panel — sitemap source, URL groups detected, Lighthouse sample list, PSI/CrUX coverage, page-HTML reachability.
- Issues lists — grouped by severity (Critical / Warnings / Info),
each grouped by check id with a
[template]badge when many posts share the same root cause. - "Copy summary" — one click → a Markdown summary of the whole audit on your clipboard, ready to paste into Slack or a chat with support.
Audit runs are backgrounded — clicking Run audit returns immediately; the UI polls and updates when the run completes (~60-120s typical). Page reloads + tab switches survive the run.
The four score categories
| Category | What it covers | Source |
|---|---|---|
| SEO | Title, meta description, canonical, h1, lang, hreflang, OG completeness, JSON-LD validity, robots-meta, alt text | Mix of CMS-DB checks + Lighthouse SEO audits (via PSI) + HTML DOM parsing |
| Performance | LCP, CLS, TBT, FCP, modern image formats (lab) + LCP, CLS, INP (real-user CrUX field data when available) | Google PageSpeed Insights — same engine + scoring that web.dev / Search Console use |
| GEO / AIO | AI-search readiness: AI-crawler robots.txt allow-list, /llms.txt presence + format, first-paragraph relevance, content freshness, structured blocks, internal links, author byline, data density | Custom checks designed for the AI-citation era |
| CMS health | Workspace config: domain set, schema.org populated, sitemap reachable + covers published posts, post slug uniqueness, translation-orphan detection | DB-side checks against your workspace state |
Performance is special. It's the median across sampled URLs of Lighthouse's published performance score — the same number you'd see if you pasted your URL into PageSpeed Insights. The other categories use Brandfine's own scoring formula because they're built from custom checks Google's Lighthouse doesn't run.
When PSI returns no data (network blip, key not configured),
Performance renders as — instead of a misleading 100. Re-run to
retry — the PsiClient does a single automatic retry on transient
failures already.
How URL discovery works
Brandfine never asks you to declare URL patterns. The audit reads
your /robots.txt, follows the Sitemap: declarations there, and
recurses one level into <sitemapindex> if present. Sitemap entries
- their
<xhtml:link rel="alternate" hreflang>siblings flatten into a single URL list, capped at 1000 URLs per run.
If robots.txt doesn't declare sitemaps (or doesn't exist), the
audit falls back to probing /sitemap.xml and /sitemap-index.xml
at conventional paths. If nothing's reachable, it audits the
homepage only and flags cms:workspace:sitemap-present as a
critical issue (no sitemap = real SEO problem worth surfacing).
URL groups — template-aware sampling
The audit clusters discovered URLs by shared path prefix: 56 URLs
under /en/services/ become one group, 23 under /blog/ become
another. Groups ≥ 3 URLs. The coverage panel surfaces detected
groups so customers can see their site's structure as the audit
sees it:
Detected 5 URL groups from the sitemap:
/en/services/ × 54
/services/ × 54
/en/ × 7
/en/legal/ × 4
/legal/ × 4Lighthouse sampling uses these groups: the audit picks one URL per
group instead of just the first N from the sitemap. That way a
performance regression on the blog template gets caught, even when
/blog/* URLs appear deep in the sitemap.
URL ↔ Post matching
For workspaces that publish posts through Brandfine, the audit
matches each sitemap URL to a cms Post by slug + locale. Matched
posts attach to per-URL check context, so post-specific checks
(translation-orphan, freshness, author-byline) can run. Unmatched
URLs — landing pages, doc pages, category indexes, or sites that
use Brandfine for analytics only — still get audited for SEO /
perf / GEO / DOM checks; they just skip post-specific concerns.
Reading the issues list
Each issue is rendered as a row:
[critical/fail]badge — severity + status (warn = amber, fail = rose, pass = green).× N postscount — how many URLs hit this check.[template]badge (violet) — when ≥3 URLs share the IDENTICAL observed value, the audit recognises this as a single template- level finding ("fix once, all instances disappear") rather than N separate problems.- Fix suggestion — actionable copy where the audit can offer one. Some checks just surface the observation.
- Affected posts list (expandable) — for non-template-level findings with multiple instances, click "Show affected posts" to see the URL + Edit link per affected post.
Severity bands
| Severity | When to fire | Score impact (per failure, max) |
|---|---|---|
critical | Real defect — page not indexable, missing required tag, no sitemap | 100 × weight |
warning | Real concern — short content, missing OG image, slug-naming issue | 30 × weight |
info | Optimization suggestion — title length tuning, byline absence, data-density | 5 × weight |
warn status (vs fail) halves the penalty — used when something
is present but suboptimal (e.g. meta description present but only
58 chars).
The "Copy summary" button
Audits surface a lot of detail. The Markdown export packs the whole report — scores, coverage, every finding with observed values + fix suggestions — into a paste-friendly format suitable for sharing in chat (with support, with an AI assistant, with your team). Click Copy summary, paste anywhere.
Example output:
# SEO Audit
Status: completed · Finished: 2026-05-31T21:48:36.918Z
URLs audited: 136 (sitemap source: sitemap-index)
Cms posts in workspace: 110
## Scores
- SEO: 92/100
- Performance: 87/100 (Lighthouse via PSI)
- GEO/AIO: 96/100
- CMS Health: 100/100
## Lighthouse sample (5 pages)
- https://example.com/
- https://example.com/en/services/uk-eta/
- ...
## Critical (1)
- **perf:lcp** [warn/critical] × 5
Fix: LCP measures when the largest content element renders. …
Variants: 5What gets sent to Google
The Performance score (and CrUX field data) come from Google
PageSpeed Insights. Each call sends the audited URL to Google's
infrastructure. The same URL Google already crawls for ranking
— there's no new data-sharing surface introduced — but if your
workspace audits URLs you'd rather Google not know about (private
staging, draft preview routes), leave GOOGLE_PSI_API_KEY unset.
The audit still runs; all perf:* and crux:* checks skip
cleanly with a clear "PSI not configured" message.
PSI free tier is 25,000 calls/day per project; each audit makes ~5 calls (one per sampled URL), so a daily-cron-across-every- workspace scenario still has years of headroom before the quota matters.
When the audit can't see your site
Audits run from Brandfine's infrastructure → your site over the
public internet. If your site requires auth, blocks unknown
user-agents at the CDN, or returns 4xx/5xx for the bot UA
BrandfineAuditBot/1.0, the audit will surface that as low
"Page HTML" coverage in the coverage panel (rendered in rose when
less than 50% of URLs return parseable HTML). Whitelist the User-Agent on
your CDN's bot-management rules if you intentionally want
Brandfine's audit to succeed.
The PSI leg runs from Google's infrastructure separately, so a Google-allowed-but-Brandfine-blocked CDN config produces partial audits (PSI metrics work, DOM checks skip). The coverage panel distinguishes these.
Recommended cadence
Run audits manually when:
- You ship a redesign or template change
- You publish a batch of new content (sitemap + cms drift detection catches "published in cms but not yet redeployed")
- You see a SERP-impressions drop in Search Console
For workspaces under active development, weekly is plenty — nothing in the audit looks at "what changed since last run" yet. Scheduled audits + trend charts are a planned future enhancement.