Skip to content

Skill · 09 of 09

audit measure, fix, re-measure, stop

The verification loop for websites built with void. Explains when to run `void lint` (static, seconds, after each significant edit) and `void audit` / `void…

skills/audit/SKILL.md149 lines2 referencesupdated 16 Sept 2026

When Claude Code loads it

The verification loop for websites built with void. Explains when to run `void lint` (static, seconds, after each significant edit) and `void audit` / `void perf|smooth|a11y|seo|geo|design` (real Chromium against a production build, before declaring done), how to read findings, severities, gates, scores and budgets, how to fix efficiently with `void rules <id>`, how to configure void.config.ts, when to stop, and how to run it in CI. Use before telling the user a website, landing page or UI change is finished; when asked to audit, test, check, verify, benchmark or score a site for performance, Core Web Vitals, accessibility, SEO or AI search; when a `void` command prints findings or exits non-zero; when setting performance budgets; or when adding a GitHub Actions quality gate.

void is the CLI in this plugin (packages/cli, binary void, runs on Bun ≥ 1.2 with a local Chromium). Every finding has a stable rule id (perf/lcp-slow, seo/metadata-in-body, lint/tw-v3-arbitrary-var). The fixes live in the speed, seo, a11y, craft and motion skills. This skill is about the loop. Full flag reference: references/commands.md. CI: references/ci.md.

The 10 rules that matter most

  1. Run void lint after every significant edit. It's static, needs no browser, and finishes in seconds. Fix its errors before moving on.
  2. Run void audit only against a production build (next build + next start, or a deployed preview). Never against next dev: dev ships unminified JS, HMR and no prefetch, so the numbers are meaningless.
  3. Use --start "cmd" --port N so void starts the server, waits for it, audits, and stops it. Don't leave background servers running, and don't pkill -f "next start" (it can kill your own shell).
  4. Read --format md (compact, grouped by rule, top 5 targets each). Use --json only when parsing programmatically.
  5. Fix in order: gates → errors → budgets → warnings. info findings are review prompts and never fail a run.
  6. Group by rule id, not by occurrence. One root cause (a layout component, a token, a helper) usually explains every instance. Fix it once.
  7. Look up unfamiliar ids with void rules (why, fix, example) before editing. Don't guess.
  8. Re-run only the category you touched (void a11y …, void seo …), then one full void audit at the end.
  9. Fix the user experience, not the metric. Never hide content from the lab, special-case headless browsers or user agents, delay loading until after measurement, or delete a feature to make a finding disappear.
  10. Stop when the stopping criteria below are met. Don't chase the last point of a noisy perf score with risky refactors. Report remaining warnings to the user instead.

When to run what

Moment Command Time Needs
After editing components, CSS, metadata, config void lint (or void lint src) 1–5 s nothing
Unsure what a finding means void rules · void rules perf (list a category) instant nothing
Finished a page or feature void --start "" --port for the categories you touched 20–90 s production build
Before saying "done" void audit --start "" --port --format md 1–4 min production build
Deployed preview / production void audit https://preview-url.example --format md 1–4 min URL
Setup problems (no Chromium, launch failure, exit code 4) void doctor 5 s nothing
Compare before/after --out before.json, fix, --out after.json, then void report after.json --format md saved reports
Borrow a reference site's design system void extract --out ./dna (see the inspire skill) 30–60 s URL

Next.js recipe:

bash
bun run build                                                        # or: npx next build
void audit --start "bun run start -p 3100" --port 3100 --format md    # or: npx next start -p 3100

If void.config.ts defines start, routes and src (the template's does), just run void audit --format md.

Categories

Category What it measures Typical first fixes
perf LCP, CLS, TBT, FCP, TTFB, JS/CSS/font/image bytes, requests, third parties, DOM size, bfcache, cache headers, compression (median of --runs, mobile + desktop) speed skill: LCP image, client JS, fonts
smooth Scripted scroll (dropped frames, long animation frames), scripted clicks (INP), layout shifts after input or scroll, animated layout properties, reduced-motion, idle rAF loops, WebGL contexts speed references rendering-smoothness.md, react-pitfalls.md
a11y axe-core WCAG 2.2 AA + best practices, keyboard focus visibility, target size on mobile, 320px reflow, zoom disabled a11y skill
seo Status codes, redirects, soft 404, title/description/canonical in , h1, links, OG and icons, robots.txt, sitemap, hreflang, HTML size seo skill + next-metadata.md
geo Raw-HTML vs rendered content (bot UAs), streamed/hidden content, JSON-LD validity/required props/visibility, AI bot robots policy, llms.txt, Markdown mirrors, answer-first/citation heuristics seo skill + structured-data.md, ai-crawlers.md
design Font families, type scale, line length, contrast of body text, palette and accent sprawl, spacing grid, radius sprawl, dark mode, AI-slop tells, horizontal overflow craft skill
lint Static source: Tailwind v4 correctness, Next 16 APIs, React render-body bugs, a11y markup, SEO files, motion hygiene the id's skill

Reading a report

text
**FAIL** (fail-on error) · overall 78 · perf 71 · smooth 88 · a11y 92 · seo 49 · geo 80 · design 85
3 errors, 9 warnings, 4 info · mobile+desktop

Budgets exceeded: jsKb@mobile 212 KB > 170 KB

**ERROR** `seo/canonical-missing` ×4: Canonical link is missing
- No <link rel="canonical"> in <head> of raw HTML · `http://localhost:3100/pricing`
- Fix: Add alternates.canonical per page via pageMetadata().
- Docs: https://void-design.vercel.app/rules/seo/canonical-missing
  • Severity: error = broken for users or crawlers (fails with the default --fail-on error); warn = measurable loss; info = heuristic or low-evidence advice.
  • Gates: a few failures cap the category score at 49 no matter what else passes. SEO gates: seo/http-status, seo/noindex, seo/canonical-missing, seo/canonical-invalid, seo/canonical-broken, seo/robots-blocks-all, seo/sitemap-url-status. GEO gates: geo/js-dependency (main content not in raw HTML), geo/jsonld-parse-error. Perf: poor LCP (> 4 s mobile, > 2.5 s desktop) or CLS > 0.25. Fix these first.
  • Budgets: any metric over its budget fails the run (exit 1) even with zero error findings.
  • Scores (0–100): per category, then weighted overall (perf 25%, a11y 20%, smooth 15%, seo 15%, design 15%, geo 10%). Perf and smooth blend metric curves with findings; other categories subtract per-finding penalties (each rule counted at most twice).
  • Exit codes: 0 pass · 1 findings ≥ --fail-on or budget exceeded · 2 usage/config error · 3 URL unreachable or server didn't start · 4 browser failed (run void doctor).
  • "Could not complete" lists audits that crashed. Treat that category as unmeasured, not as passing.

Fixing efficiently

  1. Take the top group. Run void rules if the fix isn't obvious.
  2. Find the source: target.file:line for lint; selector or html for runtime findings. Search the codebase for the selector's class names or the text.
  3. Fix the shared cause: the layout, the pageMetadata helper, the Button component, the token, the font loader.
  4. void lint, rebuild, then re-run that category only: void seo --start "…" --port N --format md.
  5. Repeat until the category passes, then move to the next. Finish with one full void audit.

Tips:

  • --routes /,/pricing,/blog/hello audits representative templates (one per route type), not every page.
  • --profile mobile halves perf/smooth time while iterating. The final run uses both profiles.
  • --runs 5 when perf numbers are near a budget. Lab variance of ±10–20% between runs is normal. Trust medians, not a single run.
  • --max-per-rule 2 keeps the markdown output short in big reports.
  • Rebuild after every code change before re-auditing. next start serves the old build.
  • A finding that you've verified is a false positive, or an intentional choice: turn it off in void.config.tsrules: { "design/no-dark-mode": "off" } with a comment explaining why. Never do this for gates.

void.config.ts

ts
// void.config.ts (project root; auto-detected). Plain object: no import needed.
/** @type {import('@void/cli/config').VoidConfig} */
const config = {
  kind: "marketing",                   // default budgets: "marketing" (JS ≤170 KB) or "app" (JS ≤300 KB)
  src: "src",                          // also used by `void lint` with no args, and linted during `void audit`
  start: { command: "bun run start -p 3100", port: 3100, readyPath: "/", timeoutMs: 30000 },
  routes: ["/", "/pricing", "/blog/hello-world", "/this-page-does-not-exist"], // include a 404 probe
  profiles: ["mobile", "desktop"],
  runs: 3,
  budgets: {                           // override defaults per profile; ms, KB (transfer), CLS unitless
    mobile: { lcp: 2500, cls: 0.1, inp: 200, tbt: 200, ttfb: 800, jsKb: 170, cssKb: 25, fontKb: 120, imageKb: 1000, htmlKb: 30 },
    desktop: { lcp: 1500, cls: 0.05, inp: 100, tbt: 100, ttfb: 600 },
  },
  rules: { "geo/no-statistics": "off" }, // severity override: "error" | "warn" | "info" | "off"
  failOn: "error",                     // "error" | "warn" | "info" | "never"
  lighthouse: false,                   // true = also run Lighthouse (optional peer dep: bun add -d lighthouse)
};
export default config;

CLI flags override the file (--kind app, --fail-on warn, --routes, --runs, --profile).

Stopping criteria (declare done only when all hold)

Check Target
void lint 0 errors
void audit exit code 0 (no errors, all budgets pass) against a production build, both profiles
Gates none failed in any category
perf score ≥ 90 mobile on marketing routes (≥ 80 app routes)
a11y score 100 (0 axe violations), plus a manual keyboard pass over dialogs, menus and forms
seo score ≥ 95
geo score ≥ 90. info heuristics reviewed, not necessarily fixed
smooth no smooth/* errors; scroll jank and INP warnings fixed or explained
design no errors; warnings reviewed against the chosen direction (craft skill)

When a target can't be met for a real reason (a required third-party script, a client-mandated video hero), say so explicitly in your summary with the measured number and the cause. Don't silence the rule.

Anti-patterns: fixing the metric instead of the experience

Don't Why it's wrong Do instead
Detect headless/Lighthouse/bot UAs and serve a lighter page Cloaking. Real users stay slow; search engines penalize it Make the real page light
Delay all JS with setTimeout(…, 5000) so TBT drops INP and real interactions get worse Ship less JS, lazyOnload third parties, facades
Start the hero at opacity: 0 to push LCP onto something else Users see a blank hero Visible hero, animate secondary elements
Add aria-label everywhere to silence axe Wrong or duplicated names hurt screen-reader users Visible text, correct native elements
aria-hidden or display:none on offending content Removes content for users Fix contrast, names or structure
Add FAQPage or fake aggregateRating to "complete" JSON-LD Policy violation; no rich result Only mark up visible, true facts
Set failOn: "never", raise budgets or turn off rules to go green The gate stops protecting users Fix, or document an explicit exception
Audit next dev, or a page with a warm cache only Wrong numbers Production build, default cold runs
Delete a feature to clear a warning (e.g. remove all motion) Loses the design intent Fix the implementation (compositor-only, gated, reduced-motion aware)

Output to the user

Finish with a short summary: the command run, pass/fail, scores per category, budgets that were close (within 10%), what you fixed (grouped by rule id), and what remains (with ids and reasons). Include the report path if you used --out.