Lighthouse runs in CI on every PR (performance, accessibility, best practices). Reports go to lhci-reports/.
Use the repo scripts:
npm run lighthouseUseful variants:
npm run lighthouse:collect
npm run lighthouse:assert
npm run lighthouse:open
npm run check:imagesWhat they do:
npm run lighthousebuilds the app and runs the full LHCI flow.npm run lighthouse:collectbuilds and collects reports only.npm run lighthouse:assertchecks the collected results against thresholds.npm run lighthouse:openopens the generated local HTML report on macOS.npm run check:imagesreports the heaviest shipped image assets and estimates WebP savings whencwebpis available.
In CI (GitHub Actions)
Nothing extra. GitHub sets GITHUB_TOKEN. You can also set:
env:
LHCI_UPLOAD__GITHUB_TOKEN: ${{ github.token }}Locally
To remove the warning:
# Option 1
LHCI_UPLOAD__GITHUB_TOKEN=ghp_xxx npx lhci autorun
# Option 2
export LHCI_UPLOAD__GITHUB_TOKEN=ghp_xxx
npx lhci autorunCreate a token: GitHub → Settings → Developer settings → Personal access tokens.
For filesystem-only output you don’t need a token; it’s only for optional GitHub status checks (e.g. upload modes).
LHCI needs a Chrome/Chromium binary. If you see Chrome installation not found, point LHCI at a local browser binary.
If Playwright browsers are already installed, this usually works on macOS:
CHROME_PATH="$HOME/Library/Caches/ms-playwright/chromium-1208/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" \
npm run lhciYou can also run collect directly while debugging:
CHROME_PATH="$HOME/Library/Caches/ms-playwright/chromium-1208/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" \
npx lhci collect --staticDistDir=./dist --numberOfRuns=1 --settings.preset=desktopIf your cached Playwright revision differs, adjust the chromium-#### segment to match the folder under ~/Library/Caches/ms-playwright/.
github.token— GitHub Actions context value.GITHUB_TOKEN— Env var set by GitHub Actions.LHCI_UPLOAD__GITHUB_TOKEN— LHCI env var for upload; passing the token enables status checks.lighthouserc.cjsreadsprocess.env.GITHUB_TOKENorprocess.env.LHCI_GITHUB_APP_TOKEN.
- Local:
./lhci-reports - CI: Reports are uploaded as the
lhci-reportsartifact.
The local npm run lighthouse and npm run lhci scripts both build the app first, then run lhci autorun.
The GitHub Actions workflow builds once in the quality job and reuses the uploaded dist artifact for Lighthouse.
The app has a skip link (“Skip to main content”) that is hidden until focused (Tab from top). It moves focus to <main id="main">. This helps keyboard and screen reader users skip repeated nav. Lighthouse/axe “bypass blocks” and main landmark checks should pass.
- Added explicit page
meta description - Added
color-schememetadata for light/dark aware browser UI - Removed the runtime Google Fonts dependency in favor of local/system font stacks
- Added Workbox runtime caches for same-origin puzzle images and fonts so repeat play starts from warm browser storage.
- Added short-lived deployment cache headers for public icons, social images, and generated manifests while keeping HTML and the service worker fresh.
- Added stable local scripts so Lighthouse can be run without remembering raw
lhcicommands - Ignored Lighthouse output folders in ESLint so lint stays reliable after audits
- Added a repo image-audit script so oversized puzzle and social assets are easy to spot before shipping
- Converted the heaviest shipped social image and several top puzzle outliers to WebP to cut transfer size sharply
Latest local desktop Lighthouse collect run on 2026-03-26 using Playwright Chromium:
- Run 1: Performance
100, Accessibility100, Best Practices100, SEO100 - Run 2: Performance
100, Accessibility100, Best Practices100, SEO100
Representative timings:
- FCP:
0.7s - LCP:
0.7s - TBT:
0ms - CLS:
0
Largest remaining opportunities from the report:
- Reduce unused JavaScript: about
180-200ms - Eliminate render-blocking resources: about
35-90ms - Reduce unused CSS: about
40ms
Current takeaway:
- The app is already comfortably fast on the measured desktop baseline.
- The biggest remaining performance lever is bundle trimming in the Play setup chunk, not interaction stutter.
Use these as the project guardrails when adding new assets:
- Puzzle images
- Ideal:
200-500 kB - Acceptable: up to
1 MB - Too large:
2 MB+
- Ideal:
- Hero / social / OG images
- Ideal:
100-250 kB - Acceptable: up to
500 kB - Too large:
1 MB+
- Ideal:
- Small UI images / icons
- Ideal: under
100 kB
- Ideal: under
Practical rule: if a puzzle image lands above 1 MB, run npm run check:images and compress it before merging. If it lands above 2 MB, treat that as a blocker unless there is a very unusual reason.
Recommended formats:
- Prefer
webpfor large photographic or illustrated puzzle assets - Keep
pngonly when you truly need lossless transparency - Use
jpgonly when it materially beatswebpfor a given asset
Current workflow:
npm run check:imagesThis prints the heaviest shipped image assets and, when cwebp is available, estimates how much smaller a WebP version would be.