By following this guide you will have an automated Lighthouse CI workflow running inside GitHub Actions that blocks pull requests when performance, accessibility, or SEO scores fall below thresholds you define. The setup takes roughly 45 minutes on a fresh repository and works with any Node.js-based web project deployed to a preview URL, including Next.js, Remix, Astro, and static sites.

What You'll Build

  • A GitHub Actions workflow that runs Lighthouse CI on every pull request and push to main
  • Automated score thresholds that fail the PR check when Performance drops below 85 or Accessibility below 90
  • A Lighthouse CI report uploaded as a GitHub Actions artifact so teams can inspect raw audit data
  • A configuration file that targets both desktop and mobile emulation profiles
  • An optional Slack notification step that posts the summary score to your team channel

Prerequisites

  • A GitHub repository with a Node.js web project (tested on Node 20+ and pnpm 9 or npm 10)
  • A staging or preview URL that GitHub Actions can reach at CI time (Vercel preview URLs work well)
  • Basic familiarity with GitHub Actions YAML syntax
  • Write access to the repository so you can add secrets and workflow files

Step 1: Install Lighthouse CI Locally

Install the Lighthouse CI CLI as a dev dependency. This ensures the version is pinned in your lockfile and consistent across local runs and CI.

npm install --save-dev @lhci/[email protected]

As of September 2026, @lhci/cli version 0.14 ships with Lighthouse 12 under the hood, which uses the updated Core Web Vitals thresholds introduced in the March 2025 ranking update. Always pin a specific version to prevent silent regressions when major releases land.

Verify the install worked:

npx lhci --version

You should see output like @lhci/cli 0.14.x.

What if the install fails with a peer dependency warning?

Run npm install --save-dev @lhci/[email protected] --legacy-peer-deps if you are on an older React or Next.js version. The CLI does not import your app code, so peer mismatches do not affect the audit results.

Step 2: Create the Lighthouse CI Configuration File

Create a file called lighthouserc.json in the root of your repository. This file controls which URLs to audit, how many runs to average, and which score thresholds to enforce.

{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "url": ["https://your-preview-url.vercel.app"],
      "settings": {
        "formFactor": "mobile",
        "throttling": {
          "rttMs": 40,
          "throughputKbps": 10240,
          "cpuSlowdownMultiplier": 4
        }
      }
    },
    "assert": {
      "preset": "lighthouse:no-pwa",
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.85 }],
        "categories:accessibility": ["error", { "minScore": 0.90 }],
        "categories:best-practices": ["warn", { "minScore": 0.90 }],
        "categories:seo": ["warn", { "minScore": 0.85 }],
        "first-contentful-paint": ["warn", { "maxNumericValue": 2000 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

The numberOfRuns: 3 setting averages three Lighthouse passes. A single pass can swing by 5 to 10 points on a shared CI runner, so three runs produce a far more stable score. The LCP threshold of 2500ms matches the Google Search Central "Good" threshold as documented in the Core Web Vitals spec.

Replace https://your-preview-url.vercel.app with a real URL. For Vercel deployments you can inject the preview URL dynamically from an environment variable, which you will do in Step 4.

Should you audit multiple pages?

Add additional entries to the url array for your most business-critical pages. A product listing page, a checkout page, and a blog post are good candidates for an e-commerce site. Keep the total URL count below five in CI to avoid long run times.

Step 3: Add a Build Script to package.json

Add a script that GitHub Actions will call to run the full Lighthouse CI flow:

{
  "scripts": {
    "lhci": "lhci autorun"
  }
}

lhci autorun reads lighthouserc.json, collects the audits, runs the assertions, and uploads the report in one command. This keeps the GitHub Actions YAML simple.

Step 4: Create the GitHub Actions Workflow File

Create .github/workflows/lighthouse.yml with the following content:

name: Lighthouse CI

on:
  pull_request:
    branches: [main, staging]
  push:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Wait for Vercel preview deployment
        uses: patrickedqvist/[email protected]
        id: wait-for-preview
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          max_timeout: 120

      - name: Run Lighthouse CI
        run: |
          npx lhci autorun \
            --collect.url=${{ steps.wait-for-preview.outputs.url }}
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

      - name: Upload Lighthouse report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: lighthouse-report
          path: .lighthouseci/
          retention-days: 14

The wait-for-vercel-preview action polls the GitHub Deployments API until Vercel marks the preview as ready, then outputs the live URL. This prevents Lighthouse from auditing a 404 page.

The if: always() on the artifact upload step ensures the raw report files are saved even when the assertion step fails. This is critical for debugging which specific audit rules caused the failure.

What if you are not using Vercel?

Replace the wait step with a hardcoded URL or with a custom step that fetches your preview URL from your hosting provider's API. Netlify, Render, and Railway all expose deployment URLs via their APIs. Store the URL in an environment variable called LHCI_URL and pass it with --collect.url=$LHCI_URL.

Step 5: Add the Required Secrets

Go to your GitHub repository, then Settings, then Secrets and variables, then Actions. Add the following secrets:

  • LHCI_GITHUB_APP_TOKEN: Generate this from the Lighthouse CI GitHub App at github.com/apps/lighthouse-ci. It lets Lighthouse post audit results as a PR status check with a detailed report link.
  • GITHUB_TOKEN: This is automatically available in all GitHub Actions workflows. You do not need to add it manually.

If you skip the Lighthouse CI GitHub App, remove the LHCI_GITHUB_APP_TOKEN env line. The workflow still runs and blocks the PR, but the detailed report link will not appear in the PR status checks panel.

Step 6: Add the Optional Slack Notification Step

If your team uses Slack, add this step after the Lighthouse CI step to post a score summary:

      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/[email protected]
        with:
          payload: |
            {
              "text": "Lighthouse CI failed on ${{ github.repository }} for PR #${{ github.event.pull_request.number }}. Check the report artifact for details."
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Add SLACK_WEBHOOK_URL to your repository secrets. You can create an incoming webhook from the Slack API dashboard at api.slack.com/messaging/webhooks.

Step 7: Commit and Open a Test Pull Request

Commit the three new files: lighthouserc.json, your updated package.json, and .github/workflows/lighthouse.yml.

git checkout -b feat/add-lighthouse-ci
git add lighthouserc.json package.json .github/workflows/lighthouse.yml
git commit -m "feat: add Lighthouse CI workflow"
git push origin feat/add-lighthouse-ci

Open a pull request on GitHub. You will see the workflow appear in the Checks tab within seconds. The full audit typically completes in 3 to 6 minutes on a standard ubuntu-latest runner, depending on the number of URLs and runs.

If your scores pass, the check turns green. If they fail, the check turns red and the PR cannot be merged until the scores improve or a team admin overrides the branch protection rule.

How do you set branch protection rules?

Go to Settings, then Branches, then Add rule for your main branch. Enable "Require status checks to pass before merging" and search for "Lighthouse CI". This makes the audit a hard gate, not just an informational check.

Common Pitfalls to Avoid

  • Auditing localhost. Lighthouse CI cannot reach localhost from a GitHub Actions runner. Always audit a deployed preview URL.
  • Single-run instability. One run on a shared runner can vary by 8 to 12 points due to CPU contention. Always use numberOfRuns: 3 at minimum.
  • Setting thresholds too tight too fast. Start with 0.75 for performance and tighten by 0.05 increments each sprint. Jumping straight to 0.95 will block your team on day one.
  • Ignoring the artifact. The raw .lighthouseci/ directory contains full JSON reports. Download the artifact and open the HTML file in a browser to see the exact failing audits.

Teams at Lenka Studio use a similar setup on client Next.js projects. Catching a 4MB image regression in a PR is far cheaper than catching it after a deploy to production.

Frequently Asked Questions

Does this work with static sites built with Astro or Eleventy?

Yes. Any site that produces a publicly accessible URL can be audited by Lighthouse CI. You may need to add a build step before the audit step if you want to audit a locally served build instead of a preview URL, using lhci collect --start-server-command="npm run serve".

Why is my Performance score lower in CI than in my local Chrome DevTools?

GitHub Actions runners apply CPU throttling by default, and the lighthouserc.json config above adds mobile network throttling on top. This reflects real user conditions more accurately than an unthrottled localhost audit. Expect CI scores to be 10 to 20 points lower than a local desktop audit.

What if I get a "No deployments found" error from the wait-for-vercel-preview step?

This usually means the Vercel integration has not been connected to the GitHub repository. Go to your Vercel project settings, navigate to Git, and reconnect the repository. The integration must have permission to post deployment status to the GitHub Deployments API.

Can I use this workflow without the Lighthouse CI GitHub App?

Yes. Remove the LHCI_GITHUB_APP_TOKEN environment variable line. The workflow still runs assertions and blocks the PR via the standard GitHub Actions check status. You just lose the inline report link that appears in the PR checks panel.

How does this differ from just running Lighthouse in Chrome DevTools manually?

Manual DevTools audits run once, on your machine, with your browser extensions loaded, and do not block code merges. This CI workflow runs on every PR, uses a standardised throttled environment, averages multiple runs, and enforces thresholds automatically. It turns a one-off check into a repeatable quality gate.

Next Steps

Once your baseline thresholds are green, tighten them by 0.05 each sprint until you are consistently at or above 90 for performance on mobile. From there, consider adding the --config.settings.screenEmulation flag to run separate desktop audits in a parallel job.

You can also pipe the JSON report into a Looker Studio dashboard using a scheduled Cloud Function, so stakeholders see score trends over time without opening GitHub. Combine this with your brand health tracking to connect technical performance scores to real business outcomes. The Lenka Studio Brand Health Score is a free assessment that helps you see how performance fits into your broader digital presence.

If you want help setting up automated performance monitoring or need a second pair of eyes on your CI configuration, the team at Lenka Studio works with SMBs across Australia, Singapore, Canada, and the US on exactly this kind of infrastructure. Get in touch and we can walk through your setup together.