TL;DR

  1. Add a workflow at .github/workflows/accessibility.yml

  2. Add lighthouserc.json with your URLs and assertions

  3. Checkout, build your project (Hugo, Symfony, …), then run Lighthouse CI — via npx --yes @lhci/cli@0.15.1 autorun or the treosh/lighthouse-ci-action

  4. Run the same checks locally before push via npx

Info

Keep in mind that URLs used in the examples are specific for my testing applications. Your applications might have other routes you want to test with lightouse.

Example for Hugo Projects

Github Workflow

name: Accessibility

on:
  push:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c #v6.4.0
        with:
          go-version-file: go.mod
          cache: true

      - uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 #v3.2.1
        with:
          extended: true

      - name: Build site
        run: hugo build --minify --gc

      - name: Lighthouse accessibility
        uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 #v12.6.2
        with:
          configPath: ./lighthouserc.json   

lighthouserc.json

{
  "ci": {
    "collect": {
      "staticDistDir": "./public",
      "url": [
        "http://localhost/",
        "http://localhost/about/",
        "http://localhost/nginx-config-tests/"
      ],
      "numberOfRuns": 1,
      "settings": {
        "onlyCategories": ["accessibility"]
      }
    },
    "assert": {
      "assertions": {
        "categories:accessibility": ["error", { "minScore": 1 }],
        "color-contrast": "error"
      }
    }
  }
}

How it works

  • staticDistDir starts a file server that is used by lighthouse. Because the build artifacts of Hugo are in public, we point to that direction.
  • I chose the url array so that I test articles and pages that use all of the different features of my theme like quotes, images, codeblocks. In consequence, all kinds of content display are covered regarding the accessibility check.
  • onlyCategories “accessibility” does, what I want - check the accessibility criteria with lighthouse. You can also use the other categories of lighthouse (seo, performance, best-practices)
  • assert: what I want to assert

Run locally with npx

Same command as in CI — build first, then invoke Lighthouse CI with a pinned version:

hugo build --minify --gc
npx --yes @lhci/cli@0.15.1 autorun

Example for Symfony Projects

Info

Please notice that I use a dockerized setup with justfile to keep things convenient. Your setup might differ, so you might need to adjust the workflow regarding commands. For example just compose pull is docker compose pull. If you run without docker, you do not need that and just have to make sure you start up your Symfony app, so the routes configured for lighthouse are accessible during the CI run, so lighthouse tests can be performed.

Github Workflow

name: Accessibility

on:
  push:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - name: Install Just
        uses: extractions/setup-just@v4
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
        with:
          persist-credentials: false
      - name: Pulling Docker images
        run: just compose pull
      - name: Building Docker images
        run: just compose build
      - name: Install dependencies
        run: just install
      - name: Start application
        run: just init-a11y
      - name: Lighthouse accessibility
        uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 #v12.6.2
        with:
          configPath: ./lighthouserc.json

lighthouserc.json

{
  "ci": {
    "collect": {
      "url": [
        "http://localhost:8080/"
      ],
      "numberOfRuns": 1,
      "settings": {
        "onlyCategories": ["accessibility"]
      }
    },
    "assert": {
      "assertions": {
        "categories:accessibility": ["error", { "minScore": 1 }],
        "color-contrast": "error"
      }
    }
  }
}

My Symfony app runs at localhost:8080 (nginx-unpriviledged). Thus, this url is used in the configuration.

Execute lighthouse locally via justfile recipe

Tip

If you are not familiar with just, check out my related article on just or the official documentation. Regarding the commands below, the variables defined at the start can be substituted in the recipes 1:1.

For example {{PHP-DB-RUN}} === docker compose -f docker/compose.yml run --rm php-fpm.

COMPOSE := 'docker compose -f docker/compose.yml'
COMPOSE-RUN := COMPOSE + ' run --rm'
PHP-DB-RUN := COMPOSE-RUN + ' php-fpm'

[private]
init-a11y *args:
	APP_ENV=prod {{COMPOSE}} up -d {{args}}
	{{DB-RUN}} mysqladmin -uroot -pChangeMe -hdb --wait=10 ping
	APP_ENV=prod {{PHP-DB-RUN}} bin/console cache:warmup --env=prod
	APP_ENV=prod {{PHP-DB-RUN}} bin/console doctrine:database:drop --force --if-exists --env=prod
	APP_ENV=prod {{PHP-DB-RUN}} bin/console doctrine:database:create --env=prod
	APP_ENV=prod {{PHP-DB-RUN}} bin/console doctrine:schema:create --env=prod
	APP_ENV=prod {{PHP-DB-RUN}} bin/console doctrine:migrations:sync-metadata-storage --no-interaction --env=prod
	APP_ENV=prod {{PHP-DB-RUN}} bin/console doctrine:migrations:version --add --all --no-interaction --env=prod

# run lighthouse accessibility checks against the home page
a11y:
	#!/usr/bin/env bash
	set -euo pipefail
	if ! curl -sf http://localhost:8080/ > /dev/null || curl -s http://localhost:8080/drives | grep -q 'sf-toolbar'; then
		echo "Starting application in prod mode for accessibility audit..."
		just init-a11y
	fi
	npx --yes @lhci/cli@0.15.1 autorun

Differences between Hugo and Symfony

The Lighthouse configuration is similar; the surrounding setup is not.

Hugo is static. You build HTML into public/ and point Lighthouse at that folder via staticDistDir. Lighthouse starts its own file server — no app process, no database, no Docker. CI is essentially: checkout → build Hugo → run Lighthouse.

Symfony is dynamic. Pages are rendered at request time, so Lighthouse needs a running application. Your lighthouserc.json lists live URLs (for example http://localhost:8080/) and CI must boot the stack first — containers, dependencies, database schema, cache warmup. That is heavier, but it audits what users actually get, including server-rendered markup and runtime-served assets.

Running Lighthouse: GitHub Action vs npx

Two approaches appear in this article; both call the same @lhci/cli under the hood.

GitHub Action (treosh/lighthouse-ci-action)npx (npx --yes @lhci/cli@0.14.0 autorun)
Used hereCIlocal runs
ProsChrome and Lighthouse preconfigured; uploadArtifacts stores HTML reports on failure; no install step in the workflow YAMLNo package.json or lockfile for Lighthouse; version pinned in the command
ConsExtra abstraction; local runs need npx (or npm) to mirror CIDownloads the CLI on cold runs; you must keep the @version suffix in sync everywhere you use it

Practical takeaway: There are three ways to use lighthouse-ci. You can run it directly via npx (which you could also do in CI). Secondly, you can use the GitHub action for CI (community developed) and rely on npx locally. As third option, you can also require @lhci/cli as dev dependency and add an npm script to run lighthouse-ci (see below).

Alternative: install via npm

Instead of npx, add @lhci/cli as a devDependency and a script in package.json, then all you need is to run npm run a11y to execute lighthouse:

"scripts": { "a11y": "hugo build --minify --gc && lhci autorun" },
"devDependencies": { "@lhci/cli": "0.15.1" }

The benefit of this approach is version control via the lockfile and faster repeat runs when cached. But you pay for it with pulling in the Node toolchain, even if the site itself is not a Node app. I would recommend the approach via npm, if you already maintain a package.json for other tooling.

Advanced Usage

Apart from accessibility checks, the lighthouse package also provides the other lighthouse checks (seo, best-practices, performance).

You can use uploadArtifacts in your GitHub workflow to see a nice html report of the lighthouse checks.

Apart from checks on code, there also is a community project that offers a Lighthouse CI Compare Action, which compares the current commit against the ancestor commit and creates a nice reporting.

Sources & Further Reading