Synthetic Monitoring with Lighthouse CI
AI generated
60fps
ms
Performance · Lighthouse CI · Synthetic Monitoring · Magento 2
Synthetic Monitoring with Lighthouse CI
Stopping performance regressions before they ship

Lighthouse CI measures performance in a controlled, repeatable way directly inside the pipeline, preventing slow deployments from ever going live. This article explains the difference from Real User Monitoring, walks through a complete lighthouserc configuration with budget assertions, covers historical trend tracking, and shows why testing only the homepage creates a dangerous false sense of security for Magento stores.

14 min. read Lighthouse CI · Budgets · CI/CD Magento 2.4.8 · Hyvä Theme · GitHub Actions

1. Synthetic Monitoring vs Real User Monitoring: two roles, not a contradiction

Synthetic Monitoring runs controlled, repeatable measurements under simulated conditions, with a fixed network throttling profile and a defined CPU slowdown factor. That control is exactly what makes it the right tool for the CI/CD pipeline: two measurements of the same code produce comparable numbers, because variables like device type, network quality, or time of day are eliminated. Real User Monitoring (RUM), by contrast, captures measurements from real sessions of real visitors on real devices and networks, indispensable for knowing what users actually experience, but unsuitable for evaluating a single pull request.

The practical difference shows up in when you learn about a problem: synthetic monitoring in the pipeline catches a regression before it deploys. RUM only catches a regression after real users have already suffered through it, but with the full breadth of real-world devices and network conditions. Relying on only one of the two leaves a gap: RUM alone means every regression only becomes visible after the fact. Synthetic monitoring alone means the lab data never gets validated against the reality of your own customer base. Lighthouse CI closes exactly the first gap by turning performance measurement into a fixed, automated part of every deployment.

2. Installing and configuring Lighthouse CI in the pipeline

Lighthouse CI (LHCI) is Google's official tooling for running Lighthouse audits in an automated, repeatable way. Installation happens via @lhci/cli as a dev dependency, and configuration lives in a lighthouserc.js or lighthouserc.json file at the project root. That file defines three key blocks: collect for the URLs to test and the measurement parameters, assert for the thresholds each metric is checked against, and upload for where the results get sent.

The number of runs per URL is critical for reliable results: a single run is subject to measurement noise from system load on the CI runner, which is why numberOfRuns: 3 is considered the minimum, with LHCI automatically taking the median. For Magento stores with server-side rendering, it's also worth using a CPU throttling profile that reflects your actual customer devices, rather than Lighthouse's default settings, which are often too generous for a desktop runner.


// lighthouserc.js: base configuration for Lighthouse CI
module.exports = {
  ci: {
    collect: {
      // Number of runs per URL, LHCI automatically takes the median
      numberOfRuns: 3,
      settings: {
        preset: 'desktop',
        // Match CPU throttling realistically to target hardware
        throttlingMethod: 'simulate',
        throttling: {
          cpuSlowdownMultiplier: 4,
        },
      },
      // Base URL gets overridden per environment (staging/preview)
      startServerCommand: 'php -S 0.0.0.0:8080 -t pub',
      startServerReadyPattern: 'Listening',
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
    upload: {
      target: 'filesystem',
      outputDir: './lhci-reports',
    },
  },
};

3. Budget assertions: making builds fail on purpose

The real value of Lighthouse CI only appears once you add budget assertions: fixed thresholds that, when exceeded, make the build fail with a non-zero exit code and block the merge. Without assertions, LHCI is just a reporting tool producing numbers nobody consistently checks. With assertions, performance becomes a property that gets enforced just as strictly as a failing unit test.

Assertions can be configured per metric at three levels: off disables the check, warn prints a warning without stopping the build, and error fails the build. For critical metrics like largest-contentful-paint or total-blocking-time, error with a numeric maxNumericValue is the right choice, while less critical audits like uses-text-compression can be introduced with warn first, so teams aren't immediately overwhelmed with a wall of red builds. A separate budget.json in the standard performance budget format complements the LHCI assertions with resource budgets per category, such as script or image size.


{
  "ci": {
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.85 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "total-blocking-time": ["error", { "maxNumericValue": 300 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "interactive": ["warn", { "maxNumericValue": 4000 }],
        "uses-text-compression": ["warn", {}],
        "uses-responsive-images": ["warn", {}]
      }
    }
  }
}

4. CI integration: GitHub Actions and GitLab CI in detail

Integrating with GitHub Actions runs through a dedicated job that picks up after the build step: the application gets built and started locally first, then lhci autorun runs the configured URLs and evaluates the assertions. It's important to only start this job after a successful static content deploy, since unbundled CSS or JavaScript artificially skews the measurements and triggers false alarms. For preview environments, it's best to test the actual staging URL rather than a local server, so CDN and cache behavior get captured realistically too.

In GitLab CI, the same principle runs through a dedicated stage in .gitlab-ci.yml, usually after deploying to a review app. Both systems support uploading the HTML reports as an artifact, so developers can open the visual Lighthouse report directly on a failed build instead of just seeing a red status line. A token for the LHCI server, or an access token for filesystem storage in a shared cache directory, should be stored as a CI secret in both cases, never checked into the repository.


# .github/workflows/lighthouse-ci.yml
name: Lighthouse CI

on:
  pull_request:
    branches: [main]

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

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Build static assets
        run: npm run build

      - name: Run Lighthouse CI against multiple page types
        run: npx lhci autorun --config=./lighthouserc.js
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

      - name: Upload Lighthouse reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lighthouse-reports
          path: ./lhci-reports

5. Historical trends: LHCI server vs filesystem storage

A single build report only answers "Is this pull request good enough?", not the more important question "Is our performance slowly degrading over weeks?" That's what historical trend tracking is for. LHCI offers two storage options: the self-hosted LHCI server stores every run in a SQL database, provides a web UI with time-series charts per metric and URL, and can automatically compare against the last green build on the target branch. That's the right approach for teams who actively want to keep an eye on performance trends.

The lightweight alternative is filesystem storage: reports get written as JSON files into an artifact directory and persisted through the CI pipeline, for example into an S3 bucket or as a CI artifact with a long retention period. Without running a server, a simple script can extract a trend across the last N builds and post it as a comment on the pull request. For smaller teams without dedicated infrastructure, this is often the more pragmatic starting point before running an LHCI server becomes worthwhile.


#!/usr/bin/env bash
# Run Lighthouse CI against multiple page types and archive the reports
set -euo pipefail

BASE_URL="https://staging.mironsoft-shop.example"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
REPORT_DIR="./lhci-reports/${TIMESTAMP}"

mkdir -p "${REPORT_DIR}"

npx lhci collect \
  --url="${BASE_URL}/" \
  --url="${BASE_URL}/women/dresses.html" \
  --url="${BASE_URL}/women/summer-dress-red.html" \
  --url="${BASE_URL}/checkout/" \
  --numberOfRuns=3

npx lhci assert --config=./lighthouserc.js

# Archive reports for historical comparison
cp -r .lighthouseci/*.json "${REPORT_DIR}/"
echo "Reports saved to ${REPORT_DIR}"

6. Testing multiple page types instead of just the homepage

The homepage of a Magento store is almost always the most heavily optimized page: it has a handful of carefully chosen images, no complex forms, and often an aggressively configured Full Page Cache. Category pages with layered navigation, product listings, and pagination, product pages with galleries, variant selectors, and cross-sell widgets, and the checkout with its many interactive form fields all have a completely different performance profile. A Lighthouse CI setup that only tests the homepage is therefore systematically measuring the least representative page in the entire store.

The url list in the collect block should cover at least four page types: the homepage, a category page with a realistic product count, a product page with a gallery and reviews, and the first checkout step. Edge cases like a faceted filter with many active filters, or a search results page with zero hits, are worth an extra entry too, since these states often load additional JavaScript logic and DOM elements. Important: for logged-in or personalized states like a checkout with items already in the cart, LHCI needs a puppeteerScript that sets cookies or fills out a login form before measurement, since Lighthouse measures as an anonymous visitor by default.

7. Common Magento pitfalls in synthetic testing

A frequent mistake with Magento stores is running Lighthouse CI against a local Docker environment with the Full Page Cache disabled, since developers typically turn the cache off for local debugging. The measured values then come out far worse than production, and assertions either trigger far too early or get set so generously they become useless in production. The target environment for Lighthouse CI should therefore always be a staging or review app with a production-like configuration, including an enabled Varnish or built-in Full Page Cache and deployed static content assets.

A second pitfall involves dynamic content: stock levels, prices, and promotional banners change between measurement runs, producing what looks like random measurement noise but is actually a content change. Fixed test fixtures with stable product SKUs and disabled time-based campaigns on the staging environment eliminate this source of error. Equally important: third-party scripts like chat widgets or A/B testing tools should either load identically to production in staging or be deliberately blocked, but never differ uncontrolled between production and staging, otherwise Lighthouse CI ends up measuring something different from what customers actually experience.

8. RUM as a complement: web-vitals in real traffic

Lighthouse CI alone doesn't capture what actually happens for real users on slow mobile networks, older devices, or with browser extensions enabled. This is exactly where Real User Monitoring with the web-vitals JavaScript library complements the synthetic pipeline checks: every real session sends its LCP, INP, and CLS values to an analytics backend, making it possible to see the actual distribution across the full customer spectrum, not just a single simulated run.

In practice, the two systems complement each other in a clear workflow: Lighthouse CI prevents a pull request with an obvious regression from ever getting merged. RUM continuously monitors whether the sum of many unremarkable changes adds up to a slow degradation over weeks, something no single build check would ever catch. When a RUM alert triggers, you can then run a targeted Lighthouse CI check against the affected page and the suspected commit range to synthetically reproduce and verify the regression before merging the fix.

Aspect Homepage-only testing Multi-page testing
Representativeness Measures the most optimized page Covers home, category, product, checkout
Checkout regressions Go completely undetected Caught before merge
Cache behavior Only measures static FPC hits Covers static and dynamic pages
JS bundle load Gallery and variant JS goes unnoticed Product page JS gets checked directly
Effort per build Low, but misleading Higher, but a reliable signal

Mironsoft

Performance monitoring, CI/CD pipelines, and Hyvä optimization for Magento stores

Ready to stop performance regressions automatically?

We set up Lighthouse CI for your Magento store, define reliable budget assertions across every relevant page type, and connect synthetic checks with Real User Monitoring for a complete performance picture.

Lighthouse CI setup

lighthouserc configuration, budgets, and multi-page testing for GitHub Actions or GitLab CI

Budget assertions

Realistic thresholds that stop builds on purpose instead of producing noise

RUM integration

web-vitals tracking and trend history as a complement to synthetic pipeline checks

10. Summary

Synthetic Monitoring with Lighthouse CI addresses one core problem: performance regressions get caught before they deploy, not after customers have already suffered through them. A reliable lighthouserc.js with multiple runs per URL, clearly defined budget assertions using error and warn thresholds, and integration into GitHub Actions or GitLab CI turns performance into a property that gets enforced just as strictly as a failing test. Historical trend tracking through an LHCI server or simple filesystem storage makes slow degradations visible that a single build check would never catch.

The decisive lever for Magento stores lies in test scope: testing only the homepage measures the least representative page in the entire store and systematically misses regressions on category, product, and checkout pages. Combined with Real User Monitoring via the web-vitals library, this produces a complete picture: synthetic monitoring as preventive control in the pipeline, RUM as continuous observation of actual user experience in the field.

Synthetic Monitoring with Lighthouse CI - The Essentials at a Glance

Synthetic vs RUM

Synthetic monitoring checks before deploy, RUM observes real users afterward. Both roles complement each other, they don't replace one another.

Budget assertions

error stops the build, warn only informs. Always configure critical metrics like LCP and TBT as error.

Historical trends

LHCI server for time-series charts and regression comparison, filesystem storage as a lightweight alternative.

Multi-page testing

Include at least home, category, product, and checkout in the collect URL list, not just the homepage.

11. FAQ: Synthetic Monitoring with Lighthouse CI

1What is the difference between Synthetic Monitoring and Real User Monitoring?
Synthetic monitoring measures in a controlled, repeatable way, ideal for the CI pipeline before deployment. RUM captures real visitors on real devices and shows actual experience after deployment.
2How do I set up Lighthouse CI in a pipeline?
Via @lhci/cli and a lighthouserc.js with the collect, assert, and upload blocks. lhci autorun runs all three steps automatically.
3What are budget assertions and how do they stop a build?
Fixed thresholds per metric in the assert block. With an error configuration, the build fails and automatically blocks the merge.
4How many runs per URL are needed for reliable results?
At least three via numberOfRuns: 3. LHCI automatically takes the median against measurement noise from CI runner load.
5Where do you store historical Lighthouse CI results for trend analysis?
Via an LHCI server with a web UI and time-series charts, or more lightly via filesystem storage with archived JSON reports.
6Why isn't it enough to only test the homepage with Lighthouse CI?
The homepage is usually the most optimized page. Category, product, and checkout pages have a different profile and go undetected by homepage-only tests.
7How do you test personalized pages like checkout with Lighthouse CI?
Via a puppeteerScript that sets cookies or adds items to the cart before measurement. Without a script, Lighthouse only measures the anonymous state.
8What typical mistakes occur when running Lighthouse CI tests against Magento stores?
Testing against local environments with the Full Page Cache disabled, plus changing stock levels and promotional banners that create measurement noise.
9Does Lighthouse CI completely replace Real User Monitoring?
No. Lighthouse CI prevents obvious regressions before a merge but doesn't capture real device and network distribution. RUM remains necessary.
10What GitHub Actions and GitLab CI steps does a Lighthouse CI integration need?
After the build and static content deploy, a job with lhci autorun against the staging URL, plus uploading the HTML reports as an artifact.