astro-post-auditv0.7.0

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-audit
pnpm add -D @casoon/astro-post-audit
yarn add -D @casoon/astro-post-audit
MITnpm 0.7.0Astro 5, 6, 7Node 18+
audit · tests/fixtures/bad
✖ 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.ansi

Quickstart

Install, register the integration, build. The product page at astro-post-audit.casoon.de has a short tour.

  1. Install @casoon/astro-post-audit as a dev dependency.
  2. Add postAudit() to the integrations in astro.config.mjs, after sitemap().
  3. Run astro build. The report follows the build; failOn decides whether findings fail it.
astro.config.mjsJavaScript
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' }),
  ],
});