Catch errors
before they go live
After every astro build, astro-post-audit reads the generated HTML and reports missing canonicals, broken links, incomplete meta tags and accessibility problems. Static analysis – no browser, no external services.
$ astro build [FAIL] astro-post-audit Errors: 1 Warnings: 1 Info: 0 Files: 12 * blog/post/index.html (2) - Missing canonical tag [canonical/missing] at `head` Remedy: Set `site` in astro.config.mjs and render <link rel="canonical" ... /> in BaseHead. - <img> missing alt attribute [a11y/img-alt] at `img[src='/photo.jpg']` Remedy: Add an `alt` prop to <Image>/<Picture> or the <img> tag.
An audit of what actually ships.
The integration hooks into astro:build:done and checks the finished files in dist/. The checker is written in Rust; on install, the npm package fetches the matching binary for macOS, Linux or Windows. It only touches the network when you switch on external link checking.
- 01After the build — it checks the HTML that ends up on your server
- 02Offline — no headless browser, no crawler, no account
- 03Configurable — presets, rule overrides and severity per rule ID
- 04Ready for CI —
failOn, baseline and SARIF for GitHub Code Scanning
Three steps to the first report.
- 1Install
Add the package
As a dev dependency. The
postinstallscript downloads the binary from GitHub Releases and verifies its SHA-256 checksum.npm i -D @casoon/astro-post-audit - 2Register
Add the integration
If you use
@astrojs/sitemap, placepostAudit()after it. Both run inastro:build:done, in array order.integrations: [sitemap(), postAudit()] - 3Build
Run the build
The audit runs with it. With
failOn: 'errors'the build stops when errors are found.astro build
What gets checked.
SEO and canonicals
Canonical present, absolute, same-origin and self-referencing; clusters of pages sharing one canonical; noindex; trailing-slash consistency.
Links, sitemap, robots.txt
Broken internal links and fragments, orphan pages, sitemap entries without a page, Disallow: /, crawl delay and conflicts between robots.txt and noindex.
HTML and headings
lang, title, viewport and meta description with length limits; exactly one H1 and no gaps in the heading hierarchy.
Accessibility heuristics
Alt text and its quality, names for links and buttons, form labels, landmarks, duplicate IDs, valid ARIA roles, aria-hidden on focusable elements.
Open Graph and JSON-LD
og:title, og:image as an absolute URL, valid twitter:card values; JSON-LD syntax, duplicate types and entity conflicts across pages.
hreflang and i18n
Self-reference, x-default, reciprocal links and existing targets; consistency of html[lang], hreflang and canonical per locale route.
Images, assets, payload
Missing width/height, lazy loading, srcset, broken asset references, JS and CSS weight per route with outlier detection.
Third-party transfers
With gdpr: true: Google Fonts, direct YouTube and Maps embeds, public CDNs and external images, plus missing SRI hashes.
Optional modules
Offline HTML5 conformance, AI visibility and llms.txt links, UX heuristics, content style, C2PA credentials and Astro/Tailwind source analysis.
From preset to production gate.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import postAudit from '@casoon/astro-post-audit';
export default defineConfig({
site: 'https://example.com',
integrations: [
sitemap(), // before postAudit()
postAudit({
preset: 'production',
failOn: 'errors',
maxWarnings: 0,
hints: { sourceFiles: true },
reports: {
json: 'audit-report.json',
sarif: 'audit.sarif',
},
rules: {
filters: { exclude: ['404.html'] },
external_links: { enabled: true, fail_on_broken: true },
},
}),
],
});# .github/workflows/build.yml
- name: Build
run: npm run build
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: audit.sarifRoll it out without breaking the build.
On older projects the first run often reports hundreds of findings. These options let you adopt it step by step.
- 01
Baseline
writeBaselinefreezes the current findings in a file. From then on, only new findings are reported. Commit the file. - 02
Warn first, block later
Enable the heuristic modules with
failOn: 'never'and a JSON report, downgrade single rules toinfoviaseverity, then switch tofailOn: 'errors'. - 03
Go-live gate
goLivereportsnoindexon live pages, wrong origins in canonicals, OG tags and the sitemap, and forbidden domains such as staging hosts. These errors cannot be downgraded. - 04
Fast local builds
New in 0.6.0:
mode: 'fast'turns off the expensive HTML5 validation.SKIP_AUDIT=1skips the audit for a single build.
Two lines for existing projects.
// 1. Run once: freeze the current findings
postAudit({ writeBaseline: true, baseline: '.audit-baseline.json' })
// 2. From then on: only new findings are reported
postAudit({ baseline: '.audit-baseline.json' })What a static audit cannot see.
astro-post-audit reads files. Anything that only exists in the browser is out of scope.
No rendering
No colour contrast, no layout, no runtime JavaScript behaviour. That takes an audit in a real browser.
Heuristics are not a WCAG audit
The accessibility rules catch common mistakes. They do not replace a WCAG conformance assessment.
Heuristic modules
i18n, crawl budget, render blocking, privacy and the structured-data graph work on assumptions and need tuning per project via severity.
Download on install
The postinstall script needs access to GitHub Releases. Air-gapped CI environments have to provide the binary in advance.
Frequently asked questions.
Does astro-post-audit fail the build on errors?
Only if you configure it to. With failOn: "errors" the build fails on errors, with "warnings" also on warnings, with "never" it never fails. maxWarnings sets an upper limit for warnings. Without these options the audit only prints a report.
Which presets are available?
standard, strict, production (an alias for strict), seo, accessibility, performance, relaxed and editorial. Individual entries under rules override the preset. relaxed is meant as a starting point for existing sites with known issues.
How is this different from a browser-based audit?
astro-post-audit analyses the static HTML in dist/ without a browser. Browser-based tools such as auditmysite check the running website and therefore see rendering, contrast, load times and JavaScript behaviour. The two approaches complement each other.
How do I turn off individual checks?
Modules and options can be set one by one in the rules object. Under rules.severity, every rule ID can be set to error, warning, info or off, for example "html/title-too-long": "off". filters.exclude skips pages by glob.
How do I introduce the audit on an existing website?
Build once with writeBaseline: true and a baseline path. The audit stores all current findings in that file. Later builds report only new findings. Deleting entries from the file re-enables the check for them.
What does astro-post-audit cost and how is it distributed?
Nothing. The package is MIT-licensed, with no API costs and no account. It is distributed via npm (0.6.0 as of September 2026) with binaries from GitHub Releases for macOS, Linux and Windows on x64 and arm64. The Rust crate on crates.io is currently at 0.5.5.
Every build checked.
One package in astro.config.mjs; the report arrives with your next astro build.