CASOON Open Source
Audit what you actually ship.
astro-post-audit runs after every astro build and checks the finished dist/ output: SEO signals, broken internal links and lightweight WCAG heuristics. Static analysis in a Rust binary, no browser, no network calls unless you enable external link checks.
npm i -D @casoon/astro-post-auditpnpm add -D @casoon/astro-post-audityarn add -D @casoon/astro-post-audit✖ astro-post-audit Errors: 14 Warnings: 2 Info: 4 Files: 1 Top issue rules: 3x links/broken 1x buttons/name-missing 1x canonical/missing 1x document/lang-missing 1x document/title-empty ● index.html (20) - Missing canonical tag [canonical/missing] at `head` Remedy: Set `site` in astro.config.mjs and render <link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} /> in your BaseHead component - Broken internal link '/broken-page/' -> '/broken-page/' (not found in dist) [links/broken] at `a[href='/broken-page/']` Remedy: Verify the route in `src/pages/` or your Content Collection slug. If the target was renamed, update the href. - Internal link contains query parameters: '/about/?ref=nav' [links/query-params] at `a[href='/about/?ref=nav']` Remedy: Remove query parameters from internal links to avoid duplicate content signals - Broken internal link '/about/?ref=nav' -> '/about/' (not found in dist) [links/broken] at `a[href='/about/?ref=nav']` Remedy: Verify the route in `src/pages/` or your Content Collection slug. If the target was renamed, update the href. - Broken internal link '/contact/' -> '/contact/' (not found in dist) [links/broken] at `a[href='/contact/']` Remedy: Verify the route in `src/pages/` or your Content Collection slug. If the target was renamed, update the href. - The <html> element has no lang attribute. [document/lang-missing] Remedy: The <html> element needs a valid lang attribute. - The <title> element is empty. [document/title-empty] Remedy: Every page needs a meaningful <title>. - The document has no viewport declaration. [zoom/viewport-missing] Remedy: A viewport must be present and must not prevent zooming. - The document has no h1 heading. [headings/h1-missing] Remedy: Headings form the outline; do not skip levels. - The image has no alt attribute. [images/alt-missing] Remedy: Informative images need descriptive alt text. - The input has no label. [forms/label-missing] Remedy: Every input needs an associated label. - The document has no main landmark. [landmarks/main-missing] Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no navigation landmark. [landmarks/navigation-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no banner landmark. [landmarks/banner-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no contentinfo landmark. [landmarks/contentinfo-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - The link has no accessible name. [links/name-missing] Remedy: Every link needs a name that describes its target. - The button has no accessible name. [buttons/name-missing] Remedy: Every button needs a name that describes what it does. - The link text "mehr" says nothing about its target. [links/generic-name] (medium confidence) Remedy: Link text should say where it leads without the surrounding sentence. - Image missing width and height attribute (causes CLS): src='/photo.jpg' [images/missing-dimensions] at `img[src='/photo.jpg']` Remedy: Add explicit width and height attributes to prevent Cumulative Layout Shift. Use <Image> from astro:assets to get them automatically. - Image has no srcset (no responsive image markup): src='/photo.jpg' [images/missing-srcset] at `img[src='/photo.jpg']` Remedy: Use <Image> or <Picture> from astro:assets to generate responsive srcset automatically.
- checks per run, listed by progress: verbose
- 32
- presets, from relaxed to production
- 8
- report formats: JSON, Markdown, SARIF, HTML
- 4
- platforms with prebuilt binaries
- 5
What it does
Checks the output, not the source
Canonicals, sitemap, robots.txt, hreflang, Open Graph and JSON-LD are read from the generated files, so the audit sees what search engines and visitors get.
Shared accessibility rules
Accessibility and document rules come from a11y-core, the same crates behind auditmysite and LiveAudit, so a finding has one id everywhere. Contrast is reported as not testable instead of passed.
Remedies in Astro terms
Each finding names the rule, the element and a fix that points to BaseHead, astro:assets, Astro.site or your Content Collections.
Rolls out gradually
Presets, per-rule severity, a baseline that reports only new findings, and a go-live gate that catches staging domains and noindex before production.
Captured from the test fixtures
All examples →✖ astro-post-audit Errors: 1 Warnings: 2 Info: 3 Files: 4 ● long-title.html (6) - The document has no main landmark. [landmarks/main-missing] Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no navigation landmark. [landmarks/navigation-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no banner landmark. [landmarks/banner-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - The document has no contentinfo landmark. [landmarks/contentinfo-missing] (medium confidence) Remedy: Landmarks structure the page for everyone who cannot see it. - Title is 143 chars (recommended max: 60) [html/title-too-long] at `title` Remedy: Shorten the title for better display in search results - Meta description is 242 chars (recommended max: 160) [html/meta-description-too-long] at `meta[name='description']` Remedy: Shorten the description for better display in search results
A baseline recorded for a clean site, then one page added: the report lists only the new page. Every output on this site is a real run of the audit binary against the fixtures in tests/fixtures, captured with examples/capture.sh.
examples/baseline.ansiQuickstart
Install, register the integration, build. The product page at astro-post-audit.casoon.de has a short tour.
- Install
@casoon/astro-post-auditas a dev dependency. - Add
postAudit()to the integrations inastro.config.mjs, aftersitemap(). - Run
astro build. The report follows the build;failOndecides whether findings fail it.
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(),
// after sitemap(): the audit checks the generated sitemap
postAudit({ preset: 'standard', failOn: 'errors' }),
],
});