Skip to content

Reference · audit

Running void in CI

audit/references/ci.md133 linesupdated 16 Sept 2026

Two gates:

  1. void lint on every push. It's fast, needs no browser or build, and catches Tailwind v4, Next 16, React and a11y markup bugs.
  2. void audit on pull requests against a production build (started by void) or the preview deployment URL.

Run perf measurements on a quiet runner, one job at a time. Parallel Chromium instances on one machine distort CPU-throttled numbers. Use runs: 3 or more and trust medians.

1. GitHub Actions: lint + audit a local production build

yaml
# .github/workflows/void.yml
name: void
on:
  pull_request:
  push:
    branches: [main]

concurrency:
  group: void-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - run: bunx void lint --format md --fail-on error

  audit:
    runs-on: ubuntu-latest
    needs: lint
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - name: Install Chromium
        run: bunx playwright-core install --with-deps chromium   # or: sudo apt-get install -y chromium
      - run: bunx void doctor
      - name: Build
        run: bun run build
        env:
          NEXT_PUBLIC_SITE_URL: https://www.example.com          # canonical/OG URLs use the production origin
      - name: Audit
        run: |
          bunx void audit \
            --start "bun run start -p 3100" --port 3100 \
            --routes /,/pricing,/blog/hello-world,/this-page-does-not-exist \
            --format md --out void-report.json > void-report.md
      - name: Job summary
        if: always()
        run: cat void-report.md >> "$GITHUB_STEP_SUMMARY"
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: void-report
          path: |
            void-report.json
            void-report.md

Notes:

  • The audit step fails the job with exit 1 on errors or exceeded budgets. That's the gate. Exit 3 (server didn't start) and 4 (browser) mean infrastructure problems: read the printed server log tail or void doctor.
  • When void.config.ts already defines start, routes and budgets (the template does), the step is just bunx void audit --format md --out void-report.json > void-report.md.
  • bunx void assumes @void/cli is a dev dependency. In this monorepo use bun run void … (root package.json script).
  • Don't set VERCEL_ENV in CI builds you audit, or the template's robots.ts serves Disallow: / and seo/robots-blocks-all fires. That's correct behavior for previews, wrong for this job.

2. Audit the Vercel preview deployment

yaml
# .github/workflows/void-preview.yml
name: void preview
on: deployment_status
jobs:
  audit:
    if: github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - run: bunx playwright-core install --with-deps chromium
      - name: Audit preview (perf, smooth, a11y, design)
        run: bunx void audit "${{ github.event.deployment_status.environment_url }}" --only perf,smooth,a11y,design --format md > void-report.md
      - if: always()
        run: cat void-report.md >> "$GITHUB_STEP_SUMMARY"

Previews correctly send Disallow: / / noindex, so run the seo and geo gates against the local production build (§1) or against production after deploy. Vercel preview protection must allow the runner, e.g. with a protection-bypass header or a public preview.

3. Baselines and regressions

bash
# main branch: save a baseline artifact
bunx void audit --start "bun run start -p 3100" --port 3100 --json > baseline.json
# PR: compare
bunx void audit --start "bun run start -p 3100" --port 3100 --json > pr.json
jq -n --slurpfile a baseline.json --slurpfile b pr.json \
  '[$a[0].scores, $b[0].scores] | {before: .[0], after: .[1]}'
jq -n --slurpfile a baseline.json --slurpfile b pr.json \
  '($a[0].findings | map(.fingerprint)) as $old | $b[0].findings | map(select(.fingerprint as $f | $old | index($f) | not)) | map({id, severity, message})'

The second jq lists findings that are new in the PR (by fingerprint = rule id + target).

4. Other CI systems

Any runner with Bun and a Chromium works:

bash
curl -fsSL https://bun.sh/install | bash
apt-get update && apt-get install -y chromium        # Debian/Ubuntu images
export VOID_CHROMIUM=/usr/bin/chromium
bun install && bun run build
bunx void audit --start "bun run start -p 3100" --port 3100 --format md

Containers: Chromium runs with --no-sandbox inside void. Give the job ≥ 2 vCPU and don't run other heavy jobs on the same machine during perf audits.

Branch / event Command failOn
Every push void lint error
Pull request void audit (local production build) error + budgets
Preview deploy void audit --only perf,smooth,a11y,design error
Nightly on production void audit https://www.example.com --runs 5 --out nightly.json warn (report only, notify on regressions)

Loosen severities in void.config.ts rules with a comment and an owner. Never turn off gate rules (seo/http-status, seo/noindex, seo/canonical-*, seo/robots-blocks-all, seo/sitemap-url-status, geo/js-dependency, geo/jsonld-parse-error) or set failOn: "never" on PR checks.