Technical Documentation: harlan-zw/unlighthouse
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Unlighthouse
Quick Setup
Run the following command:
npx unlighthouse --site <your-site>
# or PNPM
pnpm dlx unlighthouse --site <your-site>[!TIP]
Using an AI agent? Get the unlighthouse Skill on skilld.dev/gh/harlan-zw/unlighthouse.
Requirements: Node >= 22.18.0.
Getting Started
Install instructions for all integrations can be found on the docs site.
Need a hand? Join the Discord for one-on-one help.
gitignore
Unlighthouse will save your reports in outputDir,
it's recommended you .gitignore these files.
.unlighthouseDebugging
If you run into any issues with Unlighthouse, the first step should be to re-run the scan with debugging enabled.
# NPM
npx unlighthouse --site unlighthouse.dev --debug
# or PNPM
pnpm dlx unlighthouse --site unlighthouse.dev --debugDocs
Integration instructions, Guides, API and config spec can be found on docs site.
Sponsors
License
Licensed under the MIT license.
Overview (`crux-api/README.md`)
CrUX API
Used to get the CrUX historical data for a given origin.
Overview (`packages/cli/README.md`)
@unlighthouse/cli
The command-line interface and CI integration for Unlighthouse, enabling automated website scanning in development and deployment workflows.
Installation
# Use directly with npx
npx unlighthouse --site https://example.com
# Or install globally
npm install @unlighthouse/cliUsage
Basic Scanning
# Scan a website
unlighthouse --site https://example.com
# Enable debug mode
unlighthouse --site https://example.com --debug
# Mobile device simulation
unlighthouse --site https://example.com --mobileAdvanced Options
# Multiple samples with throttling
unlighthouse --site https://example.com --samples 3 --throttle
# Custom URLs and exclusions
unlighthouse --site https://example.com --urls /home,/about,/contact --exclude-urls '/admin/**'
# With custom configuration
unlighthouse --site https://example.com --config-file ./my-config.tsCI Integration
# CI mode with exit codes for failed audits
unlighthouse-ci --site https://example.com --budget 75Configuration
Create unlighthouse.config.ts in your project root:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
debug: true,
scanner: {
device: 'desktop',
throttle: false,
},
lighthouseOptions: {
onlyCategories: ['performance', 'accessibility'],
}
})Documentation
License
MIT License © 2021-PRESENT Harlan Wilton
Overview (`packages/client/README.md`)
@unlighthouse/client
The Vue-based user interface for Unlighthouse, providing an interactive dashboard to view and analyze Lighthouse scan results.
Features
- Interactive dashboard with performance metrics
- Real-time scan progress tracking
- Detailed Lighthouse reports per page
- Performance charts and visualizations
- Export capabilities
Development
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm buildDocumentation
License
MIT License © 2021-PRESENT Harlan Wilton
Overview (`packages/core/README.md`)
@unlighthouse/core
The core engine of Unlighthouse that handles website scanning, Lighthouse execution, and report generation.
Usage
Basic Usage
import { createUnlighthouse } from '@unlighthouse/core'
const unlighthouse = await createUnlighthouse({
site: 'https://example.com',
debug: true,
scanner: {
device: 'desktop',
}
})
await unlighthouse.start()With Custom Provider
import { createUnlighthouse } from '@unlighthouse/core'
const unlighthouse = await createUnlighthouse(
{ /* user config */ },
{
name: 'custom',
routeDefinitions: () => generateRouteDefinitions(),
}
)Hooks
import { useUnlighthouse } from '@unlighthouse/core'
const { hooks } = useUnlighthouse()
hooks.hook('task-complete', (path, response) => {
console.log('Task completed for:', path)
})API
createUnlighthouse(userConfig, provider?)- Initialize UnlighthouseuseUnlighthouse()- Access the global Unlighthouse contextgenerateClient(options)- Generate static client files
Documentation
License
MIT License © 2021-PRESENT Harlan Wilton
Overview (`packages/server/README.md`)
@unlighthouse/server
The server package for Unlighthouse that provides the web server and API endpoints for the scanning interface.
Usage
import { createServer } from '@unlighthouse/server'
const { server, app } = await createServer()
// server is an instance of listhen, app is an instance of h3
await unlighthouse.setServerContext({ url: server.url, server: server.server, app })
await unlighthouse.start()Documentation
License
MIT License © 2021-PRESENT Harlan Wilton
Overview (`packages/unlighthouse-ci/README.md`)
unlighthouse-ci
Dedicated CI package for Unlighthouse that provides continuous integration capabilities with budget enforcement and exit codes.
Installation
# Use directly with npx
npx unlighthouse-ci --site https://example.com
# Or install globally
npm install unlighthouse-ciUsage
Basic CI Scanning
# Run CI scan with default budget (75)
unlighthouse-ci --site https://example.com
# Custom performance budget
unlighthouse-ci --site https://example.com --budget 85
# Desktop scanning with custom output
unlighthouse-ci --site https://example.com --desktop --output-path ./lighthouse-reportsGitHub Actions Integration
name: Lighthouse CI
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Lighthouse CI
run: npx unlighthouse-ci --site ${{ secrets.SITE_URL }} --budget 80Features
- Performance budget enforcement with configurable thresholds
- Exit codes for CI/CD pipeline integration
- Static report generation for hosting
- Comprehensive logging and debugging
- Support for custom Lighthouse configurations
Configuration
Create unlighthouse.config.ts for advanced CI configuration:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
ci: {
budget: 80,
buildStatic: true,
},
lighthouseOptions: {
onlyCategories: ['performance', 'accessibility', 'seo'],
}
})Documentation
License
MIT License © 2021-PRESENT Harlan Wilton
Overview (`packages/unlighthouse/README.md`)
unlighthouse
The main package for Unlighthouse - scan your entire website with Google Lighthouse. This is a convenience package that includes the core functionality and CLI tools.
Quick Start
# Scan your website instantly
npx unlighthouse --site https://example.com
# CI mode with performance budgets
npx unlighthouse-ci --site https://example.com --budget 80What's Included
This package includes:
@unlighthouse/core- Core scanning engine@unlighthouse/cli- Command-line interface@unlighthouse/client- Web interface for results- Two binaries:
unlighthouseandunlighthouse-ci
Installation
# Global installation
npm install unlighthouse
# Project dependency
npm install unlighthouse --save-devUsage
Interactive CLI
# Basic scan
unlighthouse --site https://example.com
# With debugging and custom device
unlighthouse --site https://example.com --debug --desktop
# Custom configuration
unlighthouse --config-file unlighthouse.config.tsProgrammatic Usage
import { createUnlighthouse } from 'unlighthouse'
const unlighthouse = await createUnlighthouse({
site: 'https://example.com',
debug: true
})
await unlighthouse.start()CI Integration
# Enforce performance budgets in CI
unlighthouse-ci --site https://example.com --budget 85Configuration
Create unlighthouse.config.ts:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
device: 'desktop',
throttle: false,
},
lighthouseOptions: {
onlyCategories: ['performance', 'accessibility'],
}
})Documentation
License
MIT License © 2021-PRESENT Harlan Wilton
3.Api Doc Index (`docs/3.api-doc/index.md`)
title: "API Reference"
keywords:
- unlighthouse api
- unlighthouse programmatic
- createUnlighthouse
- useUnlighthouse
- unlighthouse hooks
- unlighthouse integration api
navigation:
title: "API Reference"
relatedPages: - path: /api-doc/config
title: Config Reference - path: /api-doc/glossary
title: Glossary - path: /guide/guides/config
title: Configuration Guide
Programmatic API for building custom integrations and extending Unlighthouse functionality. Use these functions to create custom providers, respond to scan events, and integrate with your build tools.
Core Package
Functions from @unlighthouse/core:
`createUnlighthouse()`{lang="ts"}
Type:
(userConfig: UserConfig, provider?: Provider) => Promise{lang="ts"}This is the entry point to using Unlighthouse, it will initialise Unlighthouse with the provided configuration and an optional provider.
When no provider is given, a default provider is created which will try and resolve route definitions and URLs.
tsimport { createUnlighthouse } from '@unlighthouse/core' createUnlighthouse( // config { configFile: 'mysite.config.ts' }, // provider { name: 'custom', // some custom implementation to find the route definitions routeDefinitions: () => generateRouteDefinitions(), } )
`defineUnlighthouseConfig()`{lang="ts"}
Type:
(userConfig: UserConfig) => Promise{lang="ts"}A simple define wrapper to provide typings to config definitions. This is primarily used when creating a
config fileunlighthouse.config.tsPowered by c12.
tsimport { defineUnlighthouseConfig } from 'unlighthouse/config' export default defineUnlighthouseConfig({ site: 'harlanzw.com' })
`generateClient()`{lang="ts"}
Type:
(options: GenerateClientOptions) => Promise<void>{lang="ts"}This copies over the client from
@unlighthouse/uito be used to render our scans details.It's publicly exposed to provide a tight integrations for custom client builds, such as the CI build.
tsimport { generateClient } from '@unlighthouse/core' // ... logger.info('Generating static client.') await generateClient({ static: true }) logger.success(`Static client generated at \`${unlighthouse.runtimeSettings.generatedClientPath}\`, ready for hosting.`)
`useUnlighthouse()`{lang="ts"}
Type:
() => UnlighthouseContext{lang="ts"}Unlighthouse makes use of a composition API to retain the core state. This allows you to access unlighthouse anywhere,
which is great to avoid transferring state between your logic.tsimport { useUnlighthouse } from '@unlighthouse/core' // access the lighthouse context, pick out the worker const { worker } = useUnlighthouse() // force whichever route matches home.md to be re-scanned worker.invalidateFile('/home.md')
`useLogger()`{lang="ts"}
Type:
() => void{lang="ts"}Get the global logger instance. This is useful for tight unlighthouse integrations which want to make use of the
debugconfig.tsimport { useLogger } from '@unlighthouse/core' // you need to instantiate the logger to get the instance const logger = useLogger() // force whichever route matches home.md to be re-scanned logger.debug('Something weird has happened')
@unlighthouse/server
Functions exposed from the @unlighthouse/server package.
This package is used for instances where unlighthouse is running without a provider which has an accessible web server. For instance
running Unlighthouse with the cli provider will use this package.
`createServer()`{lang="ts"}
Type:
() => Promise<void>{lang="ts"}Creates a h3 app which uses listhen as a web server.
This is used to host the API and the client.tsimport { createServer } from '@unlighthouse/server' // ... const { server, app } = await createServer() // server is an instance of listhen, app is an instance of h3 await unlighthouse.setServerContext({ url: server.url, server: server.server, app }) await unlighthouse.start()
Unlighthouse Context
Functions exposed from unlighthouse context provided by useUnlighthouse() or createUnlighthouse() .
`start()`{lang="ts"}
Type:
() => Promise{lang="ts"}Start the client and the queue worker. A server context must be provided before this function is called.
`setCiContext()`{lang="ts"}
Type:
() => Promise{lang="ts"}Running Unlighthouse via CI does not require a server or the client, so we have a special utility for it.
`setServerContext()`{lang="ts"}
Type:
(arg: ServerContextArg) => Promise{lang="ts"}To use Unlighthouse with a client, it needs a server / app to register the API and client middleware.
`setSiteUrl()`{lang="ts"}
Type:
(url: string) => void{lang="ts"}Sets the site URL that will be scanned if it's not known at initialisation.
Hooks
Unlighthouse provides hooks using hookable which allow you tailor the core behaviour.
Hooks can be accessed on the hooks property of the context and will always return a Promise<void>|void.
export type HookResult = Promise<void> | voidimport { useUnlighthouse } from '@unlighthouse/core'
const { hooks } = useUnlighthouse()
hooks.hook('task-complete', (path, response) => {
console.log('task is finished at path', path)
})`site-changed`{lang="ts"}
Type:
(site: string) => HookResult{lang="ts"}It's possible the site is not known at initialisation, this hook is called when it's set or changed.
tshooks.hook('site-changed', (site) => { // generate payload for site })
`worker-finished`{lang="ts"}
Type:
() => HookResult{lang="ts"}Called when the worker has finished processing all queued routes. Will be called multiple times if routes are re-queued.
Mostly useful for the CI environment.
tshooks.hook('worker-finished', () => { console.log('all done :)') })
`route-definitions-provided`{lang="ts"}
Type:
(routeDefinitions: any[]) => HookResult{lang="ts"}When route definitions are provided to Unlighthouse this function will be called useful for delaying internal logic
until the definitions are found.
`visited-client`{lang="ts"}
Type:
() => HookResult{lang="ts"}Called when a user visits the path of the
@unlighthouse/uifor the first time. Useful for starting the worker on-demand.ts// only start when the user wants to see the client hooks.hookOnce('visited-client', () => { unlighthouse.start() })
`task-added`{lang="ts"}
Type:
(path: string, response: UnlighthouseRouteReport) => HookResult{lang="ts"}Fired when a new task is added to the queue worker.
`task-started`{lang="ts"}
Type:
(path: string, response: UnlighthouseRouteReport) => HookResult{lang="ts"}Fired when a task has started to work.
`task-complete`{lang="ts"}
- Type:
(path: string, response: UnlighthouseRouteReport, taskName: string) => HookResult{lang="ts"}
Fired when a task has completed it's work.
`discovered-internal-links`{lang="ts"}
- Type:
(path: string, internalLinks: string[]) => HookResult{lang="ts"}
Fired when a path discovered internal links, used for "crawl" mode.
`puppeteer:before-goto`{lang="ts"}
- Type:
(page: Page) => HookResult{lang="ts"}
Fired before Lighthouse navigates the page to its URL.
Unlighthouse re-applies the configured cookies, extra headers and
user agent before it calls this hook.
Use it to prepare the page. For example, intercept requests, inject a
script, or seed extra state.
Glossary Index (`docs/glossary/index.md`)
title: "Core Web Vitals Guide"
keywords:
- core web vitals
- core web vitals seo
- what are core web vitals
- lighthouse metrics
- lighthouse core web vitals
- web vitals guide
- google core web vitals
- core web vitals ranking factor
navigation:
title: "Core Web Vitals"
relatedPages: - path: /guide/getting-started/unlighthouse-cli
title: Unlighthouse CLI - path: /guide/recipes/improving-accuracy
title: Improving Accuracy - path: /guide/guides/lighthouse
title: Lighthouse Options
Core Web Vitals are Google's key metrics for user experience and are a direct ranking factor in search results. Understanding and optimizing these metrics improves both SEO and user experience.
Core Web Vitals
Google's three key metrics that affect search rankings:
::card-group
::card{title="LCP" to="/glossary/lcp"}
Largest Contentful Paint - Measures loading performance. How quickly does the main content appear?
::
::card{title="CLS" to="/glossary/cls"}
Cumulative Layout Shift - Measures visual stability. Does content jump around unexpectedly?
::
::card{title="INP" to="/glossary/inp"}
Interaction to Next Paint - Measures responsiveness. How quickly does the page respond to clicks?
::
::
Lighthouse Performance Metrics
Additional metrics reported by Lighthouse that contribute to your performance score:
::card-group
::card{title="FCP" to="/glossary/fcp"}
First Contentful Paint - When the first content renders. Is anything happening?
::
::card{title="TTFB" to="/glossary/ttfb"}
Time to First Byte - Server response time. How fast is your backend?
::
::card{title="TBT" to="/glossary/tbt"}
Total Blocking Time - Main thread blocking during load. 30% of Lighthouse score.
::
::card{title="Speed Index" to="/glossary/speed-index"}
Speed Index - Visual loading progress. How quickly does content fill the viewport?
::
::
Lighthouse Score Weights
| Metric | Weight | Type |
|---|---|---|
| TBT | 30% | Lab |
| LCP | 25% | Core Web Vital |
| CLS | 25% | Core Web Vital |
| FCP | 10% | Lab |
| Speed Index | 10% | Lab |
Deprecated Metrics
- TTI (Time to Interactive) - Removed in Lighthouse 10. Use TBT or INP instead.
Core Web Vitals and SEO
Since 2021, Core Web Vitals have been a Google ranking factor. Sites with good Core Web Vitals may rank higher in search results and appear in Google's "Top Stories" carousel.
Google measures Core Web Vitals from real user data (Chrome User Experience Report). Improving these metrics:
- Boosts search rankings
- Improves user engagement
- Reduces bounce rates
- Increases conversions
Measuring Your Site
Use Unlighthouse CLI to audit all these metrics across your entire website in a single scan:
npx unlighthouse --site https://example.comUnlike single-page tools like PageSpeed Insights, Unlighthouse scans your entire site and identifies which pages need the most attention.
Glossary Speed Index (`docs/glossary/speed-index.md`)
title: "What is Speed Index?"
keywords:
- what is speed index
- speed index meaning
- speed index explained
- speed index lighthouse
- visual loading speed
- speed index score
navigation:
title: "Speed Index"
relatedPages: - path: /glossary/fcp
title: First Contentful Paint (FCP) - path: /glossary/lcp
title: Largest Contentful Paint (LCP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Speed Index measures how quickly visible content populates the viewport during page load. It captures the overall visual loading experience rather than a single moment in time.
What Speed Index Measures
Speed Index analyzes a video of the page loading and calculates how quickly the viewport fills with content. It considers the visual progression between frames:
- A page that renders 80% of content instantly then slowly loads the rest scores better than
- A page that loads content evenly over time
Lower scores are better. A page that shows all content immediately has a Speed Index of 0.
Speed Index Thresholds
Mobile
| Score | Rating |
|---|---|
| ≤ 3.4s | Good |
| 3.4s - 5.8s | Needs Improvement |
| > 5.8s | Poor |
Desktop
| Score | Rating |
|---|---|
| ≤ 1.3s | Good |
| 1.3s - 2.3s | Needs Improvement |
| > 2.3s | Poor |
Desktop thresholds are lower because desktop connections are typically faster.
How Speed Index Works
Lighthouse captures video frames during page load and uses the Speedline library to:
- Calculate visual completeness at each frame
- Compute the area under the visual progress curve
- Generate a score in milliseconds
The score represents the average time at which visible parts of the page are displayed.
Why Speed Index Matters
Speed Index reflects perceived load speed better than single-moment metrics. A page might have fast FCP but still feel slow if subsequent content takes long to appear.
Good Speed Index means:
- Users see content appearing quickly
- Reduced perceived wait time
- Better visual loading experience
- Contributes 10% to Lighthouse score
Speed Index vs Other Metrics
| Metric | Measures | Single point vs Range |
|---|---|---|
| FCP | First content appears | Single moment |
| LCP | Largest content appears | Single moment |
| Speed Index | Overall visual progress | Range (area under curve) |
Speed Index complements FCP and LCP by measuring the experience between these points.
Common Speed Index Issues
Render-blocking resources - CSS and JavaScript that delay initial rendering.
Large images above the fold - Unoptimized hero images that load slowly.
Web font loading - FOIT (Flash of Invisible Text) delays text rendering.
Client-side rendering - JavaScript frameworks that render content after load.
Third-party embeds - Social widgets and ads that load in the viewport.
How to Measure Speed Index
Use Unlighthouse to measure Speed Index across your entire site.
For individual pages:
- Lighthouse in Chrome DevTools
- PageSpeed Insights
- WebPageTest (original source of the metric)
::note
Speed Index can't be measured with JavaScript in the browser. It requires video capture and frame-by-frame analysis, which only lab tools like Lighthouse can perform.
::
Improving Speed Index
Key optimization strategies from Chrome DevTools documentation:
- Minimize main thread work - Reduce JavaScript execution during load
- Reduce JavaScript execution time - Optimize and split code
- Ensure text remains visible during webfont load - Use
font-display: swap
Additional strategies:
- Optimize critical rendering path - Inline critical CSS, defer non-critical
- Optimize images - Compress, use modern formats (WebP, AVIF), lazy-load below fold
- Prioritize visible content - Load above-the-fold content first
- Use SSR or SSG - Server-render initial content instead of client-side rendering
Lighthouse Weight
Speed Index contributes 10% to the overall Lighthouse Performance score:
| Metric | Weight |
|---|---|
| TBT | 30% |
| LCP | 25% |
| CLS | 25% |
| FCP | 10% |
| Speed Index | 10% |
::note
Speed Index is a lab metric, not a Core Web Vital. Focus on LCP, CLS, and INP for search ranking impact.
::
Integration Deprecations (`docs/integration-deprecations.md`)
title: "Integration Deprecations"
icon: carbon:warning-alt
keywords:
- unlighthouse migration
- unlighthouse upgrade
- unlighthouse deprecated
- unlighthouse breaking changes
navigation: false
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /integrations/ci
title: CI Integration - path: /guide/guides/config
title: Configuration
The following build tool integrations are deprecated and will be removed in v1.0:
@unlighthouse/nuxt@unlighthouse/vite@unlighthouse/webpack
::warning
Start migrating to CLI or CI integrations for continued support.
::
Background
When Unlighthouse was being developed, the goal was to make it as simple as possible to use with your development site.
To allow for this,
integrations
where added that set up Unlighthouse automatically for you.
This provided the site URL, automatic rescans on page updates and route discovery, which allowed for smarter sampling of dynamic routes.
Why Deprecate?
Simply, the integrations are too difficult to maintain, error-prone and provide low-value.
In nearly all raised issues related to integration, they weren't needed and the CLI could be used instead.
Upgrading
You should remove any of the following packages from your project.
@unlighthouse/nuxt@unlighthouse/vite@unlighthouse/webpack
Instead, you should simply use the CLI.
npx unlighthouse --site localhost:3000The HMR integration be solved by manually rescanning routes using the UI.
The route discovery
will still work when scanned in the root directory or an app with pages.
2.Integrations 0.Cli (`docs/2.integrations/0.cli.md`)
title: "Lighthouse CLI for Entire Sites"
icon: i-carbon-terminal
keywords:
- lighthouse cli
- npm lighthouse
- lighthouse npm
- lighthouse command line
- site-wide lighthouse
- npx lighthouse
- lighthouse scan all pages
navigation:
title: "CLI"
relatedPages: - path: /integrations/ci
title: CI Integration - path: /guide/guides/config
title: Configuration - path: /glossary
title: Core Web Vitals Guide
Scan your entire website from the command line with a rich interactive dashboard. Unlike the standard lighthouse CLI which tests one page at a time, Unlighthouse automatically discovers and audits all your pages.
Installation
Global install (recommended for frequent use)
npm install unlighthouseOne-time run (no install)
npx unlighthouse --site example.comUnlighthouse wraps the Lighthouse npm package to enable site-wide scanning.
Unlighthouse CLI vs Lighthouse CLI
| Feature | lighthouse CLI | unlighthouse CLI |
|---|---|---|
| Package | lighthouse |
unlighthouse |
| Pages per run | 1 | Unlimited |
| URL discovery | Manual | Automatic |
| Interactive UI | No | Yes |
| Caching | No | Yes |
| Dynamic sampling | No | Yes |
| CI/CD ready | Manual setup | Built-in |
When to use Lighthouse CLI
- Single page audits
- Quick manual checks
- Debugging specific pages
When to use Unlighthouse CLI
- Full site audits
- Pre-launch checks
- Ongoing monitoring
- CI/CD integration
Usage
Once installed globally you'll have access to Unlighthouse through the unlighthouse binary.
Run the default scan.
unlighthouse --site example.com --debugRun without caching, throttle the requests and do 3 samples.
unlighthouse --site example.com --debug --no-cache --throttle --samples 3Configuration
Configuring the CLI can be done either through the CLI arguments or through a config file.
See the Configuration section for more details and the guides.
CLI Options
| Options | |
|---|---|
-v, --version |
Display version number. |
--site |
Host URL to scan. |
--root |
Define the project root. Useful for changing where the config is read from or setting up sampling. |
--config-file |
Path to config file. |
--output-path |
Path to save the contents of the client and reports to. |
--cache |
Enable the caching. |
--no-cache |
Disable the caching. |
--desktop |
Simulate device as desktop. |
--mobile |
Simulate device as mobile. |
--user-agent |
Specify a top-level user agent all requests will use. |
--router-prefix |
The URL path prefix for the client and API to run from. |
--throttle |
Enable the throttling. |
--samples <samples> |
Specify the amount of samples to run. |
--sitemaps <sitemaps> |
Comma separated list of sitemaps to use for scanning. Providing these will override any in robots.txt. |
--urls |
Specify explicit relative paths to scan as a comma-separated list, disabling the link crawler.e.g. unlighthouse --site unlighthouse.dev --urls /guide,/api,/config |
--exclude-urls |
Route patterns to exclude as a comma-separated list. * matches one path segment, ** matches any depth. They are not regex. e.g. unlighthouse --site unlighthouse.dev --exclude-urls /guide/**,/api/** |
--include-urls |
Route patterns to include as a comma-separated list. * matches one path segment, ** matches any depth. They are not regex. e.g. unlighthouse --site unlighthouse.dev --include-urls /,/guide/** |
--enable-javascript |
When inspecting the HTML wait for the javascript to execute. Useful for SPAs. |
--disable-javascript |
When inspecting the HTML, don't wait for the javascript to execute. |
--enable-i18n-pages |
Enable scanning pages which use x-default. |
--disable-i18n-pages |
Disable scanning pages which use x-default. |
--disable-robots-txt |
Disables the robots.txt crawling. |
--disable-sitemap |
Disables the sitemap.xml crawling. |
--disable-dynamic-sampling |
Disables the sampling of paths. |
--extra-headers <headers> |
Extra headers to send with the request. Example: --extra-headers foo=bar,bar=foo |
--cookies <cookies> |
Cookies to send with the request. Example: --cookies foo=bar;bar=foo |
--auth <auth> |
Basic auth to send with the request. Example: --auth username:password |
--default-query-params |
Default query params to send with the request. Example: --default-query-params foo=bar,bar=foo |
-d, --debug |
Debug. Enable debugging in the logger. |
-h, --help |
Display available CLI options |
Config File
If you want to configure Unlighthouse, you can create a unlighthouse.config.ts file in your cwd.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'example.com',
debug: true,
scanner: {
device: 'desktop',
},
})2.Integrations 1.Ci (`docs/2.integrations/1.ci.md`)
title: "unlighthouse-ci: Bulk Lighthouse CI for Entire Sites"
icon: i-carbon-terminal-3270
keywords:
- unlighthouse-ci
- unlighthouse ci
- unlighthouse github actions
- unlighthouse gitlab ci
- bulk lighthouse ci
- site-wide lighthouse ci
- lighthouse ci entire site
- lighthouse ci bulk
- lighthouse performance budget
- ci cd lighthouse bulk
navigation:
title: "CI/CD"
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /guide/guides/generating-static-reports
title: Static Reports - path: /glossary
title: Core Web Vitals Guide
Run `unlighthouse-ci` in your pipeline
The unlighthouse-ci binary runs Lighthouse on every page of your site and fails your CI build if any score drops below a budget:
npm install @unlighthouse/cli
unlighthouse-ci --site https://staging.example.com --budget 80Exit code 1 = budget failed. Exit code 0 = all pages passed. That's the entire contract. Wire this single command into GitHub Actions, GitLab CI, CircleCI, or any pipeline that runs bash.
Why Unlighthouse CI?
| Google LHCI | Unlighthouse CI | |
|---|---|---|
| Setup | Complex config, URL manifest | One command |
| Pages | Manual list | Auto-discovers entire site |
| Budgets | Per-page config | Single threshold for all pages |
| Reports | JSON/HTML | Interactive dashboard, JSON, CSV |
Basic Usage
# Scan site, fail if any page < 75
unlighthouse-ci --site example.com --budget 75
# Generate static HTML report
unlighthouse-ci --site example.com --build-static
# Both: budget check + report
unlighthouse-ci --site example.com --budget 75 --build-staticAlready Using LHCI Server?
Keep your existing infrastructure—Unlighthouse uploads to LHCI servers:
unlighthouse-ci --site example.com \
--reporter lighthouseServer \
--lhci-host https://lhci.yourcompany.com \
--lhci-build-token $LHCI_TOKENInstallation for CI
CI environments need Chrome. You do not need to install puppeteer:
npm install @unlighthouse/cliUnlighthouse uses the system Chrome. If it finds none, it downloads a compatible Chrome to ~/.unlighthouse. See Chrome Dependency.
Performance Budgets
Simple: One Number
# Fail if ANY category on ANY page < 75
unlighthouse-ci --site example.com --budget 75Per-Category Budgets
More control in your config:
// unlighthouse.config.ts
export default defineUnlighthouseConfig({
site: 'https://example.com',
ci: {
budget: {
'performance': 70, // Performance can be lower
'accessibility': 95, // Accessibility must be high
'best-practices': 80,
'seo': 90,
},
},
})Run with just:
unlighthouse-ciReport Formats
| Format | Use Case | Flag |
|---|---|---|
json |
Simple scores for CI parsing | --reporter json |
jsonExpanded |
Full metrics breakdown | --reporter jsonExpanded |
csv |
Spreadsheet analysis | --reporter csv |
csvExpanded |
Full data in CSV | --reporter csvExpanded |
lighthouseServer |
Upload to LHCI server | --reporter lighthouseServer |
# Generate CSV for stakeholders
unlighthouse-ci --site example.com --reporter csvExpandedStatic Reports
Generate a shareable HTML dashboard:
unlighthouse-ci --site example.com --build-staticOutput goes to .unlighthouse/. Upload that folder to any static host.
Live examples:
Configuration
Configuring the CLI can be done either through the CI arguments or through a config file.
CI Options
| Options | |
|---|---|
-v, --version |
Display version number. |
--site |
Host URL to scan. |
--root |
Define the project root. Useful for changing where the config is read from or setting up sampling. |
--config-file |
Path to config file. |
--output-path |
Path to save the contents of the client and reports to. |
--budget |
Budget (1-100), the minimum score which can pass. |
--reporter <reporter> |
The report to generate from results. Options: csv, csvExpanded, json, jsonExpanded, lighthouseServer or false. Default: jsonSimple. |
--lhci-host <lhci-host> |
URL of your LHCI server. |
--lhci-build-token <lhci-build-token> |
LHCI build token, used to add data. |
--lhci-auth <lhci-auth> |
Basic auth for your LHCI server. |
--build-static |
Build a static website for the reports which can be uploaded. |
--cache |
Enable the caching. |
--no-cache |
Disable the caching. |
--desktop |
Simulate device as desktop. |
--mobile |
Simulate device as mobile. |
--user-agent |
Specify a top-level user agent all requests will use. |
--router-prefix |
The URL path prefix for the client and API to run from. |
--throttle |
Enable the throttling. |
--samples <samples> |
Specify the amount of samples to run. |
--sitemaps <sitemaps> |
Comma separated list of sitemaps to use for scanning. Providing these will override any in robots.txt. |
--urls |
Specify explicit relative paths to scan as a comma-separated list, disabling the link crawler. |
--exclude-urls |
Route patterns to exclude as a comma-separated list, such as /admin/**. Not regex. |
--include-urls |
Route patterns to include as a comma-separated list, such as /,/blog/**. Not regex. |
--enable-javascript |
When inspecting the HTML wait for the javascript to execute. Useful for SPAs. |
--disable-javascript |
When inspecting the HTML, don't wait for the javascript to execute. |
--enable-i18n-pages |
Enable scanning pages which use x-default. |
--disable-i18n-pages |
Disable scanning pages which use x-default. |
--disable-robots-txt |
Disables the robots.txt crawling. |
--disable-sitemap |
Disables the sitemap.xml crawling. |
--disable-dynamic-sampling |
Disables the sampling of paths. |
--extra-headers <headers> |
Extra headers to send with the request. Example: --extra-headers foo=bar,bar=foo |
--cookies <cookies> |
Cookies to send with the request. Example: --cookies foo=bar;bar=foo |
--auth <auth> |
Basic auth to send with the request. Example: --auth username:password |
--default-query-params |
Default query params to send with the request. Example: --default-query-params foo=bar,bar=foo |
-d, --debug |
Debug. Enable debugging in the logger. |
-h, --help |
Display available CLI options |
Config File
If you want to configure Unlighthouse, you can create a unlighthouse.config.ts file in your cwd.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'example.com',
debug: true,
scanner: {
device: 'desktop',
},
})See the Configuration section for more details and the guides.
GitHub Actions & Netlify Example
This example is for GitHub Actions and deploys a static client build to Netlify.
name: Assertions and static report
on:
workflow_dispatch:
jobs:
demo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Dependencies
run: npm install @unlighthouse/cli netlify-cli
- name: Unlighthouse assertions and client
run: unlighthouse-ci --site <your-site> --budget 75 --build-static
- name: Deploy
run: netlify deploy --dir=.unlighthouse --prod --message="New Release Deploy from GitHub Actions"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}GitLab CI Example
# .gitlab-ci.yml
lighthouse-audit:
image: node:22
script:
- npm install @unlighthouse/cli
- unlighthouse-ci --site $SITE_URL --budget 75
artifacts:
paths:
- .unlighthouse/
expire_in: 1 weekCircleCI Example
# .circleci/config.yml
version: 2.1
jobs:
lighthouse:
docker:
- image: cimg/node:22.18.0-browsers
steps:
- checkout
- run: npm install @unlighthouse/cli
- run: unlighthouse-ci --site $SITE_URL --budget 75 --build-static
- store_artifacts:
path: .unlighthouse2.Integrations 3.Nuxt (`docs/2.integrations/3.nuxt.md`)
title: "Nuxt Integration"
icon: i-logos-nuxt-icon
keywords:
- nuxt lighthouse
- nuxt performance
- nuxt core web vitals
- nuxt module lighthouse
navigation:
title: "Nuxt"
deprecated: true
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /integration-deprecations
title: Deprecation Notice - path: /guide/guides/config
title: Configuration
::warning
Deprecated: This integration will be removed in v1.0. Use CLI or CI instead. See deprecation notice.
::
Lighthouse auditing during Nuxt development with automatic route discovery and HMR support.
Install
::code-group
yarn add -D @unlighthouse/nuxtnpm install -D @unlighthouse/nuxtpnpm add -D @unlighthouse/nuxt::
Git ignore reports
Unlighthouse will save your reports in outputDir (.unlighthouse by default),
it's recommended you .gitignore these files.
.unlighthouseUsage
To begin using Unlighthouse, you'll need to add the module to buildModules.
When you run your Nuxt app, it will give you the URL of client, only once you visit the client will Unlighthouse start.
Nuxt 3
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
modules: [
'@unlighthouse/nuxt',
],
})Nuxt 2
export default {
buildModules: [
'@unlighthouse/nuxt',
],
}Type support can be added by adding the @unlighthouse/nuxt module to your plugins. Nuxt v3 will automatically add type support.
{
"compilerOptions": {
"types": [
"@unlighthouse/nuxt"
]
}
}Configuration
You can either configure Unlighthouse via the unlighthouse key in your Nuxt config, or you can provide a unlighthouse.config.ts file
in the root directory.
Example
export default {
unlighthouse: {
scanner: {
// simulate a desktop device
device: 'desktop',
}
}
}2.Integrations 4.Vite (`docs/2.integrations/4.vite.md`)
title: "Vite Integration"
icon: i-logos-vitejs
keywords:
- vite lighthouse
- vite performance
- vite plugin lighthouse
navigation:
title: "Vite"
deprecated: true
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /integration-deprecations
title: Deprecation Notice - path: /guide/guides/config
title: Configuration
::warning
Deprecated: This integration will be removed in v1.0. Use CLI or CI instead. See deprecation notice.
::
Lighthouse auditing within your Vite dev environment with real-time performance feedback.
Install
::code-group
yarn add -D @unlighthouse/vitenpm install -D @unlighthouse/vitepnpm add -D @unlighthouse/vite::
Git ignore reports
Unlighthouse will save your reports in outputDir (.unlighthouse by default),
it's recommended you .gitignore these files.
.unlighthouseUsage
To begin using Unlighthouse, you'll need to add the plugin to plugins.
When you run your Vite app, it will give you the URL of client, only once you visit the client will Unlighthouse start.
import Unlighthouse from '@unlighthouse/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
Unlighthouse({
// config
})
]
})Providing Route Definitions
If you're using the vite-plugin-pages plugin, you can provide route definitions to Unlighthouse.
You will need to hook into the plugin using the following code.
import { useUnlighthouse } from 'unlighthouse'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
Pages({
// ...
onRoutesGenerated(routes) {
// tell Unlighthouse about the routes
const unlighthouse = useUnlighthouse()
if (unlighthouse?.hooks)
unlighthouse.hooks.callHook('route-definitions-provided', routes)
}
}),
]
})Configuration
You can either configure Unlighthouse via the plugin, or you can provide a config file file
in the root directory.
Example
import Unlighthouse from '@unlighthouse/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
Unlighthouse({
scanner: {
// simulate a desktop device
device: 'desktop',
}
})
]
})2.Integrations Webpack (`docs/2.integrations/webpack.md`)
title: "Webpack Integration"
icon: i-logos-webpack
keywords:
- webpack lighthouse
- webpack performance
- webpack plugin lighthouse
navigation:
title: "Webpack"
deprecated: true
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /integration-deprecations
title: Deprecation Notice - path: /guide/guides/config
title: Configuration
::warning
Deprecated: This integration will be removed in v1.0. Use CLI or CI instead. See deprecation notice.
::
Lighthouse auditing for webpack-based applications during development.
Install
::code-group
yarn add -D @unlighthouse/webpacknpm install -D @unlighthouse/webpackpnpm add -D @unlighthouse/webpack::
Git ignore reports
Unlighthouse will save your reports in outputDir (.unlighthouse by default),
it's recommended you .gitignore these files.
.unlighthouseUsage
To begin using Unlighthouse, you'll need to add extend your webpack configuration.
When you run your webpack app, it will give you the URL of client, only once you visit the client will Unlighthouse
start.
webpack.config.js example
import Unlighthouse from '@unlighthouse/webpack'
export default {
// ...
plugins: [
Unlighthouse({
// config
}),
],
}CJS example
const Unlighthouse = require('@unlighthouse/webpack')
export default {
// ...
plugins: [
Unlighthouse({
// config
}),
],
}Configuration
You can either configure Unlighthouse via the plugin, or you can provide a config file
in the root directory.
Example
import Unlighthouse from '@unlighthouse/webpack'
export default {
// ...
plugins: [
Unlighthouse({
scanner: {
// simulate a desktop device
device: 'desktop',
}
}),
],
}3.Api Doc Config (`docs/3.api-doc/config.md`)
title: "Configuration Reference"
keywords:
- unlighthouse config
- unlighthouse options
- unlighthouse configuration
- lighthouse config options
- unlighthouse api
navigation:
title: "Config Reference"
relatedPages: - path: /guide/guides/config
title: Configuration Guide - path: /api-doc/glossary
title: Glossary - path: /integrations/cli
title: CLI Integration
Complete reference for all Unlighthouse configuration options with types, defaults, and usage examples. For implementation guides, see Configuration Guide or CLI Arguments.
::note
Deprecated integration-specific options (Nuxt, Vite, webpack) are not documented here. Use CLI or CI integrations instead.
::
Root Options
`site`{lang="ts"}
- Type:
string{lang="ts"}
The site that will be scanned.
`root`{lang="ts"}
- Type:
string{lang="ts"} - Default:
cwd(){lang="ts"}
The path that we'll be performing the scan from, this should be the path to the app that represents the site.
Using this path we can auto-discover the provider
`cache`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
Should reports be saved to the local file system and re-used between runs for the scanned site.
Note: This makes use of cache-bursting for when the configuration changes, since this may change the report output.
`configFile`{lang="ts"}
- Type:
string|null{lang="ts"} - Default:
null{lang="ts"}
Load the configuration from a custom config file. By default, it attempts to load configuration
from unlighthouse.config.ts.
You can set up multiple configuration files for different sites you want to scan.
For example:
staging-unlighthouse.config.tsproduction-unlighthouse.config.ts
`outputPath`{lang="ts"}
- Type:
string{lang="ts"} - Default:
./.unlighthouse{lang="ts"}
Where to emit lighthouse reports and the runtime client.
Unlighthouse clears this folder before a scan without cache, such as every unlighthouse-ci run. It refuses to clear a folder that it did not create, so point it at a new or empty folder.
`debug`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
false{lang="ts"}
Display the loggers' debug messages.
`auth`{lang="ts"}
- Type:
false | { username: string, password: string }{lang="ts"} - Default:
false{lang="ts"}
Optional basic auth credentials.
`cookies`{lang="ts"}
- Type:
false | CookieParam[]{lang="ts"} - Default:
false{lang="ts"}
Provide cookies to be set for Axios and Puppeteer requests.
`extraHeaders`{lang="ts"}
- Type:
false | Record<string, string>{lang="ts"} - Default:
false{lang="ts"}
Provide extra headers to be set for Axios and Puppeteer requests.
`userAgent`{lang="ts"}
- Type:
string{lang="ts"} - Default:
undefined{lang="ts"}
Provide a custom user agent for all network requests.
`defaultQueryParams`{lang="ts"}
- Type:
false | QueryObject{lang="ts"} - Default:
false{lang="ts"}
Query params to add to every request.
`hooks`{lang="ts"}
- Type:
NestedHooks{lang="ts"} - Default:
{}{lang="ts"}
Hooks to run. See the Hooks section for more information.
`routerPrefix`{lang="ts"}
- Type:
string{lang="ts"} - Default:
''{lang="ts"}
The URL path prefix for the client and API to run from.
Useful when you want to serve the application from an existing integrations server.
For example, you could run Unlighthouse from /__unlighthouse .
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
routerPrefix: '/__unlighthouse',
})`apiPrefix`{lang="ts"}
- Type:
string{lang="ts"} - Default:
/api/{lang="ts"}
The path that the API should be served from.
`urls`{lang="ts"}
- Type:
string[]|(() => string[])|(() => Promise<string[]>){lang="ts"} - Default:
[]{lang="ts"}
Provide a list of URLs that should be used explicitly. Will disable sitemap and crawler.
CI Options
Change the behaviour of unlighthouse in CI mode.
`ci.budget`{lang="ts"}
- Type:
number|Record, number>{lang="ts"} - Default:
null{lang="ts"}
Provide a budget for each page as a numeric total score, or an object mapping the category to the score.
Should be a number between 1-100.
For example, if you wanted to make sure all of your pages met a specific accessibility score, you could do:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
ci: {
budget: {
accessibility: 90,
},
},
})`ci.buildStatic`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
false{lang="ts"}
Injects the required data into the client files, so it can be hosted statically.
Combine this with uploading to a site, and you can see the results of your unlighthouse scan on a live site.
Client Options
See Modifying client for more information.
`client.columns`{lang="ts"}
- Type:
Record{lang="ts"}
Modify the default columns used on the client.
`client.groupRoutesKey`{lang="ts"}
- Type:
string{lang="ts"} - Default:
route.definition.name{lang="ts"}
Which key to use to group the routes.
Discovery Options
See Route Definitions for more information.
`discovery.pagesDir`{lang="ts"}
- Type:
string{lang="ts"} - Default:
./pages{lang="ts"}
The location of the page files that will be matched to the routes.
Note: This is for fallback behaviour when the integration doesn't provide a way to gather the route definitions.
`discovery.supportedExtensions`{lang="ts"}
- Type:
string{lang="ts"} - Default:
['vue', 'md']{lang="ts"}
Which file extensions in the pages dir should be considered.
Scanner Options
`scanner.customSampling`{lang="ts"}
- Type:
Record<string, RouteDefinition>{lang="ts"} - Default:
{}{lang="ts"}
Setup custom mappings for a regex string to a route definition.
This is useful when you have a complex site which doesn't use URL path segments
to separate pages.
See custom sampling for more information.
`scanner.ignoreI18nPages`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
When the page HTML is extracted and processed, we look for an x-default link to identify if the page is an i18n
copy of another page.
If it is, then we skip it because it would be a duplicate scan.
`scanner.maxRoutes`{lang="ts"}
- Type:
number|false{lang="ts"} - Default:
200{lang="ts"}
The maximum number of routes that should be processed.
This helps avoid issues when the site requires a specific
configuration to be able to run properly
`scanner.include`{lang="ts"}
- Type:
(string|RegExp)[]|null{lang="ts"} - Default:
null{lang="ts"}
Paths to explicitly include from the search, this will exclude any paths not listed here.
A string is a route pattern, not a regex: * matches one path segment and ** matches any depth. Pass a RegExp for regex matching.
The crawler starts from /, so keep / in the list when you rely on the crawler.
See Include URL Patterns for more information.
`scanner.exclude`{lang="ts"}
- Type:
(string|RegExp)[]|null{lang="ts"} - Default:
null{lang="ts"}
Paths to ignore from scanning. Strings are route patterns, the same as scanner.include.
See Exclude URL Patterns for more information.
`scanner.skipJavascript`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
Does javascript need to be executed in order to fetch internal links and SEO data.
Disabling this can speed up scans but may break the parsing.
See Handling SPAs for more information.
`scanner.samples`{lang="ts"}
- Type:
number{lang="ts"} - Default:
1{lang="ts"}
How many samples of each route should be done. This is used to improve false-positive results.
See Run Lighthouse Multiple Times
and Improving Accuracy for more information.
`scanner.throttle`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
Should lighthouse run with throttling enabled. This is an alias for manually configuring lighthouse.
Local sites, such as localhost and 127.0.0.1, run unthrottled unless you set this option. An explicit true or false always wins.
See Network Throttling for more information.
`scanner.crawler`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
Should the crawler be used to detect URLs. This will parse the HTML of scanned pages for internal links and queue
them for scanning.
See URL Discovery for more information.
`scanner.dynamicSampling`{lang="ts"}
- Type:
number|false{lang="ts"} - Default:
8{lang="ts"}
When a route definition is provided, you're able to configure the worker to sample the dynamic routes to avoid
redundant route reports.
See Change Dynamic Sampling Limit for more information.
`scanner.robotsTxt`{lang="ts"}
- Type:
boolean{lang="ts"} - Default:
true{lang="ts"}
Should the robots.txt file be used for configuration.
Sitemap paths and disallows paths will be used to configure the scanner.
`scanner.sitemap`{lang="ts"}
- Type:
boolean | string[]{lang="ts"} - Default:
true{lang="ts"}
Either an array of sitemap paths, or a boolean to enable/disable sitemap scanning.
By default, when true is provided or an empty array, it will try and load the sitemap from /sitemap.xml.
Note: If you have robotsTxt enabled it will load sitemap config from here.
`scanner.device`{lang="ts"}
- Type:
boolean | string{lang="ts"} - Default:
mobile{lang="ts"}
Alias to switch the viewport dimensions used for scanning. Set to desktop for a viewport of 1350×950. Set to false if you want to manually configure it through lighthouseOptions.formFactor.
See Device Configuration for more information.
Lighthouse Options
Changes the default behaviour of Google Lighthouse.
See Configure Google Lighthouse for more information.
Puppeteer Options
Change the behaviour of puppeteer.
See puppeteer.connect(options)
for all available configurations.
Puppeteer Cluster Options
Change the behaviour of puppeteer-cluster.
By default the concurrency will be set on the CPU cores you have available.
See Cluster.launch(options) for available
configuration.
3.Api Doc Glossary (`docs/3.api-doc/glossary.md`)
title: "API Glossary"
keywords:
- unlighthouse api
- unlighthouse glossary
- route definition
- unlighthouse provider
- unlighthouse types
navigation:
title: "API Glossary"
relatedPages: - path: /api-doc
title: API Reference - path: /api-doc/config
title: Config Reference - path: /guide/guides/route-definitions
title: Route Definitions Guide
Key terms, types, and concepts used throughout the Unlighthouse API and documentation.
Core Concepts
Route Definition
A route definition maps page files (Vue components, markdown files, etc.) to their corresponding URL paths, enabling intelligent dynamic sampling and better organization.
The page component has multiple representations:
- static route - name matches the path (/about.vue -> /about/),
- dynamic route - a query is used to generate a set of paths (/posts/:id.vue -> /posts/my-first-post/)
- catch-all route where any missed paths will be caught (/404.vue -> /some-missing-page)
Additional meta-data is provided to give more context of how the mapping behaves, such as which layout to use, which
asset chunk it belongs to.
Different frameworks represent routes differently, This one is based on Nuxt.js
export interface RouteDefinition {
name: string
path: string
component?: string
componentBaseName?: string
chunkName?: string
_name?: string
layout?: string
}Provider
A provider is an integration of Unlighthouse to a specific context, such as a framework or an environment.
Each provider has their own unique name and defines how they will provide URLs and route definitions to Unlighthouse.
export interface Provider {
/**
* Used to debug.
*/
name?: string
/**
* To match a URL path to a route definition we need a router. Different definitions need different routes.
*/
mockRouter?: MockRouter | ((routeDefinitions: RouteDefinition[]) => MockRouter)
/**
* The collection of route definitions belonging to the provider. These can be inferred but aren't 100% correct,
* frameworks that can provide these should do so.
*/
routeDefinitions?: RouteDefinition[] | (() => RouteDefinition[] | Promise<RouteDefinition[]>)
}Route Report
A fairly rigid representation of the puppeteer cluster task results (extractHtmlPayload, runLighthouseTask),
combined with the normalised route.
export interface UnlighthouseRouteReport {
/**
* The mapping of tasks with their status.
*/
tasks: Record<UnlighthouseTask, UnlighthouseTaskStatus>
/**
* Path to where the artifacts from a URL scan are saved.
*/
artifactPath: string
/**
* URL of where the artifacts are stored, for static client access.
*/
artifactUrl: string
/**
* The route (URL Path) that the report belongs to.
*/
route: NormalisedRoute
/**
* A unique representation of the route, useful for the API layer.
*/
reportId: string
/**
* The lighthouse result, only set once the task is completed.
*/
report?: LighthouseReport
/**
* The SEO meta-data, only set once the html payload has been extracted and passed.
*/
seo?: {
alternativeLangDefault?: string
title?: string
description?: string
internalLinks?: number
externalLinks?: number
favicon?: string
og?: {
description?: string
title?: string
image?: string
}
}
}Unlighthouse Context
The context is provided by the createUnlighthouse() or useUnlighthouse() functions. It provides the central
API to interacting with the behaviour of Unlighthouse.
/* Detailed source-code truncated for AI context efficiency. */Mock Router
Unlighthouse provides intelligent sampling which relies on knowing which URLs map to which files in your project.
To achieve this, it needs to create its own router with your files to test any URL that comes through.
Different integrations will have different requirements from the router.
For example, different frameworks will resolve files that contain substitutes
(for example /posts/[post].vue may work in one framework but not another).
export interface MockRouter { match: (path: string) => RouteDefinition }Worker
Task
The worker will queue a route to run with multiple tasks. A task is a queued job and has their own id and status.
Unlighthouse has two core tasks:
inspectHtmlTaskwhich dumps the HTML of the URL and extracts SEO data from it (title, description, image, internal links, etc)runLighthouseTaskruns the actual lighthouse process on the URL
See cluster.task(fn) for more details.
/**
* Tasks that Unlighthouse will run, used to track their status.
*/
export type UnlighthouseTask = 'inspectHtmlTask' | 'runLighthouseTask'
/**
* Each task ran by unlighthouse (extractHtmlPayload, runLighthouseTask) has a specific status which we can expose.
*/
export type UnlighthouseTaskStatus = 'waiting' | 'in-progress' | 'completed' | 'failed'Client
Columns
A column will generally be either a direct mapping to a lighthouse audit (such as console errors) or a computed mapping to
multiple lighthouse audits (such as image issues).
It can also exist as a mapping to the SEO meta-data (such as meta description).
export interface UnlighthouseColumn {
/**
* The column header name.
*/
label: string
/**
* If the user hovers over the label they'll see a tooltip for extra context.
*/
tooltip?: string
/**
* A component instance which should be used to render the column cells contents.
*/
component?: () => Promise<unknown>
/**
* The key within the UnlighthouseRouteReport that maps to the column, used for automatic value inferring.
*/
key?: string
/**
* Column sizing definition, needed for a responsive UI.
*/
cols?: Partial<Record<WindiResponsiveClasses, number>>
/**
* Can the column can be sorted?
*
* @default false
*/
sortable?: boolean
/**
* The key within the UnlighthouseRouteReport that is used to sort the column. This will default to the key if not provided.
*/
sortKey?: string
/**
* Extra classes that should be added to the column.
*/
classes?: string[]
}Glossary Cls (`docs/glossary/cls.md`)
title: "What is Cumulative Layout Shift (CLS)?"
keywords:
- what is cumulative layout shift
- cls meaning
- cumulative layout shift explained
- cls core web vitals
- layout shift
- cls score
- fix cls
navigation:
title: "CLS"
relatedPages: - path: /glossary/lcp
title: Largest Contentful Paint (LCP) - path: /glossary/inp
title: Interaction to Next Paint (INP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Cumulative Layout Shift measures visual stability—how much page content unexpectedly moves during loading. It's one of Google's three Core Web Vitals and affects both user experience and search rankings.
What CLS Measures
CLS quantifies unexpected layout shifts that occur without user interaction. A layout shift happens when a visible element changes position from one rendered frame to the next.
The score is calculated by multiplying:
- Impact fraction - How much of the viewport was affected
- Distance fraction - How far the elements moved
Higher scores mean more disruptive shifts.
CLS Score Thresholds
| Score | Rating |
|---|---|
| ≤ 0.1 | Good |
| 0.1 - 0.25 | Needs Improvement |
| > 0.25 | Poor |
Google recommends a CLS score of 0.1 or less for at least 75% of page visits.
Why CLS Matters
Layout shifts frustrate users. You've experienced it: reading an article when an ad loads and pushes content down, causing you to lose your place—or worse, clicking the wrong button.
Poor CLS leads to:
- Accidental clicks
- User frustration
- Lost trust and higher bounce rates
- Lower search rankings
Common CLS Causes
Images without dimensions - When images load without width/height attributes, the browser doesn't know how much space to reserve.
Ads and embeds - Third-party content that injects itself into the page without reserved space.
Web fonts - Font swapping causes text to reflow when custom fonts load.
Dynamically injected content - JavaScript that inserts content above existing elements.
Animations - CSS animations that change element size or position.
How to Measure CLS
Use Unlighthouse to measure CLS across your entire site at once. This helps identify problematic pages quickly.
For individual page testing:
- Chrome DevTools Performance panel
- Lighthouse in Chrome
- PageSpeed Insights
- Web Vitals Chrome extension
Measure in Browser
Run this in your browser console to track CLS in real-time. Based on webperf-snippets.
type Rating = 'good' | 'needs-improvement' | 'poor'
function rateValue(score: number): Rating {
return score <= 0.1 ? 'good' : score <= 0.25 ? 'needs-improvement' : 'poor'
}
let cls = 0
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries() as (PerformanceEntry & { hadRecentInput: boolean, value: number })[]) {
// Only count shifts without recent user input
if (!entry.hadRecentInput) {
cls += entry.value
}
}
})
observer.observe({ type: 'layout-shift', buffered: true })
// Check current CLS anytime
function getCLS() {
const rating = rateValue(cls)
console.log(`CLS: ${cls.toFixed(4)} (${rating})`)
return cls
}
// Log final CLS when user leaves page
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
observer.takeRecords()
getCLS()
}
})
getCLS()Preventing Layout Shifts
Key strategies to maintain visual stability:
- Always set image dimensions - Use
widthandheightattributes or CSS aspect-ratio - Reserve space for ads - Use fixed containers for ad slots
- Preload fonts - Use
font-display: optionalor preload critical fonts - Avoid inserting content above existing content - Add new elements below the fold or use transforms
- Use CSS transforms for animations - Transform doesn't trigger layout recalculation
::tip
Use aspect-ratio CSS property for responsive images:
img {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}::
Glossary Fcp (`docs/glossary/fcp.md`)
title: "What is First Contentful Paint (FCP)?"
keywords:
- what is first contentful paint
- fcp meaning
- first contentful paint explained
- fcp lighthouse
- fcp score
- improve fcp
navigation:
title: "FCP"
relatedPages: - path: /glossary/lcp
title: Largest Contentful Paint (LCP) - path: /glossary/ttfb
title: Time to First Byte (TTFB) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
First Contentful Paint measures the time from navigation start to when any content first renders on screen. It's a Lighthouse performance metric that indicates when users first see something happening.
What FCP Measures
FCP tracks when the browser renders the first piece of DOM content. This includes:
- Text (any font rendering)
- Images (including background images)
- SVG elements
- Non-white canvas elements
The timer starts when the user initiates navigation and stops at the first content render. This includes time spent on redirects, DNS lookup, connection setup, and TTFB.
FCP Score Thresholds
| Score | Rating |
|---|---|
| ≤ 1.8s | Good |
| 1.8s - 3.0s | Needs Improvement |
| > 3.0s | Poor |
Google recommends an FCP of 1.8 seconds or less for at least 75% of page visits.
FCP vs LCP
FCP and LCP measure different moments:
| Metric | Measures | Answers |
|---|---|---|
| FCP | First content render | "Is anything happening?" |
| LCP | Largest content render | "Is the main content loaded?" |
A page can have fast FCP (spinner appears quickly) but slow LCP (actual content takes longer). Both matter for user experience.
Why FCP Matters
FCP marks when users perceive the page is loading. Before FCP, users see a blank screen and may assume the page is broken or slow.
A fast FCP:
- Reassures users something is happening
- Reduces perceived wait time
- Lowers early abandonment
- Contributes to overall Lighthouse score (10% weight)
Common FCP Issues
Slow server response - High TTFB delays everything, including FCP.
Render-blocking resources - CSS and synchronous JavaScript in <head> block first paint.
Large CSS files - Browser must parse all CSS before rendering any content.
Web font loading - Text may be invisible until fonts download if using font-display: block.
Too many redirects - Each redirect adds network round-trips before content can load.
How to Measure FCP
Use Unlighthouse to measure FCP across your entire site.
For individual pages:
- Chrome DevTools Performance panel
- Lighthouse in Chrome
- PageSpeed Insights
- Web Vitals Chrome extension
Measure in Browser
Run this in your browser console to see FCP timing.
type Rating = 'good' | 'needs-improvement' | 'poor'
function rateValue(ms: number): Rating {
return ms <= 1800 ? 'good' : ms <= 3000 ? 'needs-improvement' : 'poor'
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
const rating = rateValue(entry.startTime)
console.log(`FCP: ${(entry.startTime / 1000).toFixed(2)}s (${rating})`)
}
}
})
observer.observe({ type: 'paint', buffered: true })Improving FCP
Key optimization strategies:
- Reduce server response time - Optimize TTFB with caching and CDNs
- Eliminate render-blocking resources - Defer non-critical CSS/JS, inline critical CSS
- Minify CSS - Remove unused styles and compress what remains
- Preconnect to origins - Use
<link rel="preconnect">for third-party domains - Avoid redirects - Each redirect adds latency
- Optimize font loading - Use
font-display: swaporoptional
::note
FCP is a Lighthouse lab metric, not a Core Web Vital. However, improving FCP typically improves LCP, which is a Core Web Vital and ranking factor.
::
Glossary Inp (`docs/glossary/inp.md`)
title: "What is Interaction to Next Paint (INP)?"
keywords:
- what is interaction to next paint
- inp meaning
- interaction to next paint explained
- inp core web vitals
- inp vs fid
- inp score
- improve inp
navigation:
title: "INP"
relatedPages: - path: /glossary/lcp
title: Largest Contentful Paint (LCP) - path: /glossary/cls
title: Cumulative Layout Shift (CLS) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Interaction to Next Paint measures how quickly your page responds to user interactions. It replaced First Input Delay (FID) as a Core Web Vital in March 2024 and is now a Google ranking factor.
What INP Measures
INP tracks the latency of all clicks, taps, and keyboard interactions throughout a page's lifecycle, then reports a value representative of the overall experience (typically the worst interaction, with some outlier handling).
The measurement includes:
- Input delay - Time from interaction to event handler start
- Processing time - Time to run event handlers
- Presentation delay - Time to render the next frame
INP Score Thresholds
| Score | Rating |
|---|---|
| ≤ 200ms | Good |
| 200ms - 500ms | Needs Improvement |
| > 500ms | Poor |
Google recommends an INP of 200 milliseconds or less for at least 75% of page visits.
INP vs FID
FID only measured the first interaction's input delay—ignoring processing and presentation time entirely. A page could have great FID but terrible responsiveness afterward.
INP is more comprehensive:
- Measures all interactions, not just the first
- Includes the full interaction lifecycle
- Better represents actual user experience
Why INP Matters
Users expect instant feedback. When they click a button and nothing happens for 500ms, they assume it's broken and click again—or leave.
Good INP means:
- Responsive, fluid interactions
- Users trust the interface works
- Lower frustration and abandonment
- Better search rankings
Common INP Issues
Long JavaScript tasks - Any task over 50ms blocks the main thread and delays interactions.
Heavy event handlers - Click handlers that do too much work before updating the UI.
Hydration delays - SPAs that look interactive but aren't until JavaScript loads.
Third-party scripts - Analytics, ads, and widgets competing for main thread time.
Large DOM size - More elements mean slower style recalculation and layout.
How to Measure INP
Unlighthouse reports responsiveness metrics across your site. For INP specifically, you need field data since it measures real user interactions.
Measurement tools:
- Chrome User Experience Report (CrUX) - Real user data
- Web Vitals Chrome extension - Your own interactions
- PageSpeed Insights - Field data section
- Performance panel in DevTools - Interaction traces
Measure in Browser
Run this in your browser console to track INP as you interact with the page.
type Rating = 'good' | 'needs-improvement' | 'poor'
function rateValue(ms: number): Rating {
return ms <= 200 ? 'good' : ms <= 500 ? 'needs-improvement' : 'poor'
}
let worstInp = 0
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries() as (PerformanceEntry & {
duration: number
interactionId: number
name: string
})[]) {
// Only track actual interactions (has interactionId)
if (!entry.interactionId)
continue
if (entry.duration > worstInp) {
worstInp = entry.duration
const rating = rateValue(worstInp)
console.log(`INP: ${worstInp}ms (${rating}) - ${entry.name}`)
}
}
})
observer.observe({ type: 'event', buffered: true, durationThreshold: 16 })
console.log('INP tracking active. Interact with the page to measure.')::note
Lab tools like Lighthouse can't fully measure INP because it requires real user interactions over time. Use field data for accurate INP scores.
::
Improving INP
Key optimization strategies:
- Break up long tasks - Use
setTimeoutorscheduler.yield()to yield to the main thread - Defer non-critical work - Move analytics and non-essential code out of interaction handlers
- Use web workers - Offload heavy computation off the main thread
- Optimize event handlers - Do the minimum work needed, then update UI
- Reduce DOM size - Fewer elements mean faster rendering
- Debounce rapid interactions - Avoid processing every keystroke
// Yield to main thread during long tasks
async function processItems(items) {
for (const item of items) {
process(item)
// Let browser handle pending interactions
await scheduler.yield()
}
}Glossary Lcp (`docs/glossary/lcp.md`)
title: "What is Largest Contentful Paint (LCP)?"
keywords:
- what is largest contentful paint
- lcp meaning
- largest contentful paint explained
- lcp core web vitals
- lcp score
- improve lcp
navigation:
title: "LCP"
relatedPages: - path: /glossary/cls
title: Cumulative Layout Shift (CLS) - path: /glossary/inp
title: Interaction to Next Paint (INP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Largest Contentful Paint measures how long it takes for the largest visible content element to render on screen. It's one of Google's three Core Web Vitals and directly impacts your search rankings.
What LCP Measures
LCP tracks the render time of the largest image, video, or text block visible in the viewport. Common LCP elements include:
- Hero images
- Featured video thumbnails
- Large heading text blocks
- Above-the-fold content images
The metric starts when the page begins loading and ends when the largest element finishes rendering.
LCP Score Thresholds
| Score | Rating |
|---|---|
| ≤ 2.5s | Good |
| 2.5s - 4.0s | Needs Improvement |
| > 4.0s | Poor |
Google recommends achieving an LCP of 2.5 seconds or less for at least 75% of page visits.
Why LCP Matters
LCP directly correlates with perceived load speed. Users don't care about technical metrics—they care about seeing content. A fast LCP means:
- Better user experience
- Lower bounce rates
- Higher search rankings (it's a ranking factor)
- Improved conversion rates
Common LCP Issues
Slow server response - Time to First Byte (TTFB) delays everything downstream.
Render-blocking resources - CSS and JavaScript that block the main thread delay LCP elements.
Unoptimized images - Large, uncompressed images take longer to download and render.
Client-side rendering - SPAs that render content via JavaScript often have poor LCP.
How to Measure LCP
Use Unlighthouse to measure LCP across your entire site at once, rather than checking pages individually.
For individual page testing:
- Chrome DevTools Performance panel
- Lighthouse in Chrome
- PageSpeed Insights
- Chrome User Experience Report (CrUX) for field data
Measure in Browser
Run this in your browser console to track LCP in real-time. Based on webperf-snippets.
type Rating = 'good' | 'needs-improvement' | 'poor'
function rateValue(ms: number): Rating {
return ms <= 2500 ? 'good' : ms <= 4000 ? 'needs-improvement' : 'poor'
}
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries() as PerformanceEntry[]
const lastEntry = entries.at(-1) as PerformanceEntry & {
startTime: number
element?: Element
url?: string
size?: number
}
if (!lastEntry)
return
const navEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
const activationStart = navEntry?.activationStart || 0
const lcpTime = Math.max(0, lastEntry.startTime - activationStart)
const rating = rateValue(lcpTime)
console.log(`LCP: ${(lcpTime / 1000).toFixed(2)}s (${rating})`)
if (lastEntry.element) {
console.log('LCP Element:', lastEntry.element)
lastEntry.element.style.outline = '3px dashed lime'
}
})
observer.observe({ type: 'largest-contentful-paint', buffered: true })Improving LCP
Key optimization strategies:
- Optimize your server - Use caching, CDNs, and efficient backend code
- Preload critical resources - Use
<link rel="preload">for LCP images - Compress images - Use modern formats (WebP, AVIF) and proper sizing
- Remove render-blocking resources - Defer non-critical CSS/JS
- Use SSR or SSG - Avoid client-side rendering for above-the-fold content
::note
LCP is measured in the field (real users) and lab (synthetic tests). Field data from CrUX is what Google uses for rankings.
::
Glossary Tbt (`docs/glossary/tbt.md`)
title: "What is Total Blocking Time (TBT)?"
keywords:
- what is total blocking time
- tbt meaning
- total blocking time explained
- tbt lighthouse
- main thread blocking
- tbt score
- reduce tbt
navigation:
title: "TBT"
relatedPages: - path: /glossary/inp
title: Interaction to Next Paint (INP) - path: /glossary/fcp
title: First Contentful Paint (FCP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Total Blocking Time measures the total time the main thread was blocked during page load. It's the heaviest-weighted Lighthouse metric (30%) and indicates how responsive a page will feel during the loading phase.
What TBT Measures
TBT counts the "blocking" portion of all Long Tasks between FCP and when the page becomes reliably interactive. A Long Task is any JavaScript task that runs for more than 50ms.
For each Long Task, only the time beyond 50ms counts toward TBT:
Task duration: 70ms → Blocking time: 20ms (70 - 50)
Task duration: 250ms → Blocking time: 200ms (250 - 50)
Task duration: 30ms → Blocking time: 0ms (under threshold)While the main thread is blocked, the browser can't respond to user input like clicks or taps.
TBT Score Thresholds
| Score | Rating |
|---|---|
| ≤ 200ms | Good |
| 200ms - 600ms | Needs Improvement |
| > 600ms | Poor |
Lighthouse tests on simulated mobile hardware. A TBT under 200ms indicates the page should feel responsive during load.
TBT vs INP
TBT and INP both measure responsiveness, but differently:
| Metric | When | What | Type |
|---|---|---|---|
| TBT | During load | Main thread blocking | Lab metric |
| INP | Throughout session | Actual interaction latency | Field metric |
TBT is a lab proxy for interactivity during load. INP measures real user interactions over time. Low TBT often correlates with good INP, but not always.
Why TBT Matters
TBT has the highest weight in Lighthouse performance scoring at 30%. It directly reflects whether users can interact with your page during load.
High TBT means:
- Clicks and taps are delayed or ignored
- Scrolling may feel janky
- Users think the page is frozen
- Poor Lighthouse performance score
Common TBT Issues
Large JavaScript bundles - More code means more parsing and execution time.
Unoptimized third-party scripts - Analytics, ads, and widgets competing for main thread.
Heavy frameworks - Client-side rendering with large framework overhead.
Synchronous operations - Layout thrashing, forced reflows, blocking API calls.
No code splitting - Loading JavaScript that isn't needed for initial view.
How to Measure TBT
Use Unlighthouse to measure TBT across your entire site.
For individual pages:
- Lighthouse in Chrome DevTools
- PageSpeed Insights - Lab data section
- WebPageTest
Measure in Browser
Run this in your browser console to track Long Tasks that contribute to TBT.
let tbt = 0
let taskCount = 0
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Only the blocking portion (time over 50ms) counts
const blockingTime = entry.duration - 50
if (blockingTime > 0) {
tbt += blockingTime
taskCount++
console.log(`Long Task: ${entry.duration.toFixed(0)}ms (blocking: ${blockingTime.toFixed(0)}ms)`)
}
}
})
observer.observe({ type: 'longtask', buffered: true })
// Check total TBT anytime
function getTBT() {
const rating = tbt <= 200 ? 'good' : tbt <= 600 ? 'needs-improvement' : 'poor'
console.log(`TBT: ${tbt.toFixed(0)}ms from ${taskCount} long tasks (${rating})`)
return tbt
}
console.log('TBT tracking active. Call getTBT() to see current total.')Improving TBT
Key optimization strategies:
- Break up Long Tasks - Split large functions using
setTimeout,requestIdleCallback, orscheduler.yield() - Reduce JavaScript - Remove unused code, tree-shake dependencies
- Code split - Load only what's needed for initial view
- Defer third-party scripts - Load analytics and ads after critical content
- Use web workers - Move heavy computation off the main thread
- Optimize CSS - Large stylesheets can block the main thread during parsing
// Break up a long task
async function processLargeArray(items: Item[]) {
for (const item of items) {
processItem(item)
// Yield to main thread periodically
if (items.indexOf(item) % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0))
}
}
}::warning
TBT is only measured in lab conditions. Real-world interactivity is measured by INP. Optimize for both, but prioritize INP since it's a Core Web Vital and ranking factor.
::
Lighthouse Weight
TBT has the largest impact on your Lighthouse Performance score:
| Metric | Weight |
|---|---|
| TBT | 30% |
| LCP | 25% |
| CLS | 25% |
| FCP | 10% |
| Speed Index | 10% |
Glossary Ttfb (`docs/glossary/ttfb.md`)
title: "What is Time to First Byte (TTFB)?"
keywords:
- what is time to first byte
- ttfb meaning
- time to first byte explained
- ttfb optimization
- server response time
- improve ttfb
- reduce ttfb
navigation:
title: "TTFB"
relatedPages: - path: /glossary/fcp
title: First Contentful Paint (FCP) - path: /glossary/lcp
title: Largest Contentful Paint (LCP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
Time to First Byte measures how long it takes for the browser to receive the first byte of response from the server. It's a foundational metric that affects all other loading performance metrics.
What TTFB Measures
TTFB captures the total time for these phases:
- Redirect time - Processing any HTTP redirects
- Service worker startup - If applicable
- DNS lookup - Resolving the domain name
- Connection setup - TCP handshake
- TLS negotiation - SSL/HTTPS handshake
- Request/response - Time until first response byte arrives
A high TTFB delays everything downstream: FCP, LCP, and full page load.
TTFB Score Thresholds
| Score | Rating |
|---|---|
| ≤ 800ms | Good |
| 800ms - 1800ms | Needs Improvement |
| > 1800ms | Poor |
Google recommends a TTFB of 800 milliseconds or less.
Why TTFB Matters
TTFB is the starting point for all other metrics. Nothing can render until the browser receives that first byte. For SPAs that rely on JavaScript rendering, fast TTFB is especially critical since client-side rendering adds additional time on top.
Impact of slow TTFB:
- Delays all subsequent loading metrics
- Extends time to first render
- Hurts perceived performance
- Can indicate server or infrastructure problems
Common TTFB Issues
Slow server processing - Complex database queries, unoptimized code, or insufficient server resources.
No caching - Regenerating responses that could be cached.
Geographic distance - Server far from users without CDN.
DNS resolution - Slow or unreliable DNS provider.
Too many redirects - Each redirect adds a full round-trip.
Missing HTTP/2 or HTTP/3 - Older protocols have more overhead.
How to Measure TTFB
Use Unlighthouse to audit TTFB across your entire site.
For individual pages:
- Chrome DevTools Network panel (look for "Waiting for server response")
- PageSpeed Insights - Shows TTFB in field data
- WebPageTest for detailed waterfall analysis
Measure in Browser
Run this in your browser console to see TTFB with sub-part breakdown. Based on webperf-snippets.
type Rating = 'good' | 'needs-improvement' | 'poor'
function rateValue(ms: number): Rating {
return ms <= 800 ? 'good' : ms <= 1800 ? 'needs-improvement' : 'poor'
}
new PerformanceObserver((list) => {
const nav = list.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
const ttfb = nav.responseStart
const rating = rateValue(ttfb)
console.log(`TTFB: ${ttfb.toFixed(0)}ms (${rating})`)
// Sub-parts breakdown
console.log('Breakdown:')
console.log(` DNS: ${(nav.domainLookupEnd - nav.domainLookupStart).toFixed(0)}ms`)
console.log(` TCP: ${(nav.connectEnd - nav.connectStart).toFixed(0)}ms`)
console.log(` SSL: ${(nav.connectEnd - (nav.secureConnectionStart || nav.connectStart)).toFixed(0)}ms`)
console.log(` Server: ${(nav.responseStart - nav.requestStart).toFixed(0)}ms`)
}).observe({ type: 'navigation', buffered: true })Improving TTFB
Key optimization strategies:
- Use a CDN - Serve content from edge locations closer to users
- Enable caching - Cache responses at CDN, server, and application levels
- Optimize server code - Profile and fix slow database queries, inefficient code
- Upgrade hosting - More CPU, memory, or faster infrastructure
- Use HTTP/2 or HTTP/3 - Modern protocols reduce connection overhead
- Minimize redirects - Each redirect adds a full round-trip
- Optimize DNS - Use a fast, reliable DNS provider
- Preconnect to origins -
<link rel="preconnect">for known third-party domains
::note
TTFB is not a Core Web Vital, but it directly impacts LCP which is. A slow TTFB makes it nearly impossible to achieve good LCP scores.
::
TTFB Sub-Parts
When debugging slow TTFB, identify which phase is the bottleneck:
| Phase | Optimization |
|---|---|
| DNS Lookup | Use faster DNS, enable DNS prefetching |
| TCP Connection | Enable HTTP/2+, use CDN |
| TLS Negotiation | Enable TLS 1.3, session resumption |
| Server Response | Optimize backend, add caching |
Glossary Tti (`docs/glossary/tti.md`)
title: "What is Time to Interactive (TTI)?"
keywords:
- what is time to interactive
- tti meaning
- time to interactive explained
- tti deprecated
- tti lighthouse
- tti removed
navigation:
title: "TTI (Deprecated)"
relatedPages: - path: /glossary/tbt
title: Total Blocking Time (TBT) - path: /glossary/inp
title: Interaction to Next Paint (INP) - path: /guide/recipes/improving-accuracy
title: Improving Accuracy
::warning
Time to Interactive (TTI) was removed from Lighthouse 10 in February 2023. Use TBT for lab measurement and INP for field measurement of interactivity.
::
Time to Interactive measured when a page became fully interactive. It was a Lighthouse metric from 2017-2023 before being deprecated due to measurement issues.
What TTI Measured
TTI marked the point when:
- The page displayed useful content (FCP fired)
- Event handlers registered for visible elements
- The page responded to interactions within 50ms
The metric identified a 5-second "quiet window" where no Long Tasks occurred and fewer than 2 network requests were in flight.
Why TTI Was Deprecated
Google removed TTI in Lighthouse 10 because:
Overly sensitive to outliers - A single late network request or Long Task could dramatically inflate TTI, even if the page felt interactive.
Better alternatives exist - LCP and Speed Index better indicate when content feels loaded. TBT measures main-thread availability more robustly.
Poor correlation with real experience - A page could have terrible TTI but still feel responsive to users, or vice versa.
Field measurement issues - TTI required long observation periods and was impractical to measure in real user monitoring.
What Replaced TTI
For Lab Testing
Total Blocking Time (TBT) - Measures cumulative main thread blocking during load. More robust, less sensitive to outliers.
When TTI was removed, its 10% score weight shifted to CLS, making the new Lighthouse weights:
| Metric | Weight |
|---|---|
| TBT | 30% |
| LCP | 25% |
| CLS | 25% |
| FCP | 10% |
| Speed Index | 10% |
For Field Measurement
Interaction to Next Paint (INP) - Measures actual interaction responsiveness across the entire page lifecycle. Became a Core Web Vital in March 2024.
TTI Thresholds (Historical)
When TTI was active, thresholds were:
| Score | Rating |
|---|---|
| ≤ 3.8s | Good |
| 3.8s - 7.3s | Needs Improvement |
| > 7.3s | Poor |
Still Need TTI?
If you have legacy systems that rely on TTI:
- It's still available in Lighthouse JSON output with
score: 0 - It's hidden from the HTML report
- Scripted access to the JSON value continues to work
However, you should migrate to TBT or INP for meaningful interactivity measurement.
Migration Guide
| Old Approach | New Approach |
|---|---|
| TTI in Lighthouse | Use TBT (30% of score) |
| TTI in field/RUM | Use INP (Core Web Vital) |
| TTI CI assertions | Switch to TBT thresholds |
For most sites, the removal of TTI improved Lighthouse scores since pages typically score better on CLS than they did on TTI.
Measuring Interactivity Today
Use Unlighthouse to measure TBT across your site for lab data.
For real-user interactivity:
- Chrome User Experience Report (CrUX) for INP field data
- PageSpeed Insights shows both lab TBT and field INP
- Web Vitals library for custom RUM
1.Guide 1.Getting Started 0.Installation (`docs/1.guide/1.getting-started/0.installation.md`)
title: "Unlighthouse CLI - Site-Wide Lighthouse"
keywords:
- unlighthouse cli
- npm lighthouse
- lighthouse npm
- npx lighthouse
- install lighthouse
- bulk lighthouse test
- site-wide lighthouse audit
- core web vitals testing tool
navigation:
title: "CLI"
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /guide/guides/config
title: Configuration - path: /glossary
title: Core Web Vitals Glossary
Stop checking pages one at a time. Run a single command and get Lighthouse scores for your entire site in minutes.
npx unlighthouse --site example.com[!TIP]
Using an AI agent? Get the unlighthouse Skill on skilld.dev/gh/harlan-zw/unlighthouse.
That's it. Unlighthouse will crawl your site, find every page, run Lighthouse on each one, and open a dashboard where you can watch results stream in live.
::note
New to Lighthouse? Check the Core Web Vitals glossary for what LCP, CLS, and INP actually mean.
::
Quick Start
Pick your package manager:
::code-group
npx unlighthouse --site https://mysite.compnpm dlx unlighthouse --site https://mysite.comyarn dlx unlighthouse --site https://mysite.com::
A browser window opens automatically showing your scan progress. Results appear as each page completes—no waiting for the full scan to finish.
What Happens Behind the Scenes
- Discovery — Unlighthouse reads your sitemap and crawls internal links to find every page
- Parallel scanning — Multiple Chrome instances run Lighthouse audits simultaneously
- Live results — Scores stream to your dashboard as each page completes
- Smart sampling — Similar pages (like blog posts) are sampled to avoid redundant scans
Requirements
- Node.js 22.18+
- Chrome — Uses your system Chrome. If missing, downloads Chromium automatically.
Install Globally (Optional)
If you scan sites frequently:
npm install unlighthouse
unlighthouse --site example.comCommon First Scan Issues
Nothing happening?
Add --debug to see what's going on:
npx unlighthouse --site example.com --debugWSL Users
Chrome can be tricky in WSL. If you see connection errors, check the WSL troubleshooting guide.
Slow scans?
Your first scan downloads route data and warms up Chrome. Subsequent scans use caching and are much faster.
Configure Your Scan
Create unlighthouse.config.ts in your project root:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
samples: 3, // Run each page 3x and average (more accurate)
throttle: true, // Simulate real network conditions
exclude: ['/admin/*', '/api/*'], // Skip these paths
},
})Now just run unlighthouse without flags—it reads your config automatically.
See all configuration options.
What's Next?
- CI Integration — Run scans on every deploy, fail builds on performance regressions
- Large Sites — Scanning thousands of pages efficiently
- Authentication — Scan pages behind login
Get Help
- Discord — Quick questions and community help
- GitHub Issues — Bug reports and feature requests
1.Guide 1.Getting Started 0.Unlighthouse Cli (`docs/1.guide/1.getting-started/0.unlighthouse-cli.md`)
title: "Unlighthouse CLI - Site-Wide Lighthouse"
keywords:
- unlighthouse cli
- npm lighthouse
- lighthouse npm
- npx lighthouse
- install lighthouse
- bulk lighthouse test
- site-wide lighthouse audit
- core web vitals testing tool
navigation:
title: "CLI"
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /guide/guides/config
title: Configuration - path: /glossary
title: Core Web Vitals Glossary
Stop checking pages one at a time. Run a single command and get Lighthouse scores for your entire site in minutes.
npx unlighthouse --site example.comThat's it. Unlighthouse will crawl your site, find every page, run Lighthouse on each one, and open a dashboard where you can watch results stream in live.
::note
New to Lighthouse? Check the Core Web Vitals glossary for what LCP, CLS, and INP actually mean.
::
Quick Start
Pick your package manager:
::code-group
npx unlighthouse --site https://mysite.compnpm dlx unlighthouse --site https://mysite.comyarn dlx unlighthouse --site https://mysite.com::
A browser window opens automatically showing your scan progress. Results appear as each page completes—no waiting for the full scan to finish.
What Happens Behind the Scenes
- Discovery — Unlighthouse reads your sitemap and crawls internal links to find every page
- Parallel scanning — Multiple Chrome instances run Lighthouse audits simultaneously
- Live results — Scores stream to your dashboard as each page completes
- Smart sampling — Similar pages (like blog posts) are sampled to avoid redundant scans
Requirements
- Node.js 22.18+
- Chrome — Uses your system Chrome. If missing, downloads Chromium automatically.
Install Globally (Optional)
If you scan sites frequently:
npm install unlighthouse
unlighthouse --site example.comCommon First Scan Issues
Nothing happening?
Add --debug to see what's going on:
npx unlighthouse --site example.com --debugWSL Users
Chrome can be tricky in WSL. If you see connection errors, check the WSL troubleshooting guide.
Slow scans?
Your first scan downloads route data and warms up Chrome. Subsequent scans use caching and are much faster.
Configure Your Scan
Create unlighthouse.config.ts in your project root:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
samples: 3, // Run each page 3x and average (more accurate)
throttle: true, // Simulate real network conditions
exclude: ['/admin/*', '/api/*'], // Skip these paths
},
})Now just run unlighthouse without flags—it reads your config automatically.
See all configuration options.
What's Next?
- CI Integration — Run scans on every deploy, fail builds on performance regressions
- Large Sites — Scanning thousands of pages efficiently
- Authentication — Scan pages behind login
Get Help
- Discord — Quick questions and community help
- GitHub Issues — Bug reports and feature requests
1.Guide 1.Getting Started 1.Integrations (`docs/1.guide/1.getting-started/1.integrations.md`)
title: "Integrations"
keywords:
- unlighthouse integration
- lighthouse integration
- lighthouse build tool
- automated lighthouse
- lighthouse github actions
- lighthouse ci cd
navigation:
title: "Integrations"
relatedPages: - path: /integrations/cli
title: CLI Integration - path: /integrations/ci
title: CI Integration - path: /guide/guides/config
title: Configuration
Choose how to integrate Unlighthouse into your workflow: manual CLI scans for development, automated CI/CD checks for deployment pipelines, or build tool integration for framework-specific setups.
Command Line
| Provider | Use Case |
|---|---|
| CLI | Scan a production site such as unlighthouse.dev. You can manually provide a project mapping for routes definitions. |
| CI | Run scans on sites based on automation events, i.e releasing and make assertions on scores. Can also be used to generate report sites such as inspect.unlighthouse.dev. |
Build tools / Frameworks
::warning
Deprecation Notice: Build tool integrations are deprecated and will be removed in v1.0. We recommend using the CLI or CI integrations instead. Learn more about deprecations
::
| Provider | Features |
|---|---|
| Nuxt.js | Hot Module Reloading, Automatic Route Discovery |
| Vite | Hot Module Reloading, Automatic Route Discovery |
| webpack | Hot Module Reloading |
1.Guide 1.Getting Started How It Works (`docs/1.guide/1.getting-started/how-it-works.md`)
title: "How Unlighthouse Works"
keywords:
- how unlighthouse works
- lighthouse site scanner
- automated lighthouse testing
- site-wide lighthouse audit
- bulk lighthouse scan
- parallel lighthouse testing
navigation:
title: "How It Works"
relatedPages: - path: /guide/guides/url-discovery
title: URL Discovery - path: /guide/guides/config
title: Configuration - path: /glossary
title: Core Web Vitals Glossary
Unlighthouse automates the entire site audit process: it finds all your pages, runs Lighthouse on each one in parallel, then shows you aggregated results in a real-time dashboard.
How It Starts
1. Setup
First, Unlighthouse loads your config file using the c12 package. This supports TypeScript config files and gives you a global defineUnlighthouseConfig function for type support:
::code-group
const unlighthouse = await createUnlighthouse(config)import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
samples: 3,
throttle: true,
},
})::
Browser Workers
Unlighthouse creates a pool of Chrome browsers using puppeteer-cluster:
- Opens multiple Chrome instances
- Each one can scan a different page
- Runs scans in parallel to go faster
2. Report Site Setup
Unlighthouse starts a local web server so you can see the results:
// Create server for the UI client
const { server, app } = await createServer()
await unlighthouse.setServerContext({
url: server.url,
server: server.server,
app
})API
The unrouted API handles:
- Sending scan updates to your browser
- Serving Lighthouse reports
- Managing the scanning process
Web App
A Vite web app that:
- Shows scan progress in real-time
- Displays all the Lighthouse scores
- Lets you filter and explore results
3. The Actual Scanning
When It Starts
Unlighthouse can start in two ways:
::code-group
// CLI mode - starts immediately
unlighthouse.start()// Integration mode - waits for first client access
hooks.hookOnce('visited-client', () => {
unlighthouse.start()
})::
Finding Your Pages
Unlighthouse finds pages to scan in a few ways:
- Route Files: Reads your framework's route files (like Next.js pages)
- Robots.txt: Checks your robots.txt file for sitemap links
- Sitemap.xml: Gets all URLs from your sitemap
- Crawling: If no sitemap, it starts from your homepage and follows links
::tip
Having a sitemap.xml makes scanning much faster and finds more pages.
::
What Happens to Each Page
For every URL it finds, Unlighthouse does two things:
Step 1: Quick HTML Check
- Makes a simple HTTP request to get the HTML
- Grabs basic info like title, meta tags
- Looks for more links to scan
Step 2: Full Lighthouse Scan
- Opens the page in Chrome
- Runs all the Lighthouse tests (including Core Web Vitals like LCP, CLS, INP)
- Saves the report as HTML and JSON files
Live Updates
While scanning, the report page updates in real-time:
- Shows how many pages are done
- Updates the progress bar
- Shows results as soon as each page finishes
1.Guide Guides 0.Config (`docs/1.guide/guides/0.config.md`)
title: "Configuration"
keywords:
- unlighthouse config
- unlighthouse configuration
- unlighthouse.config.ts
- lighthouse config file
- lighthouse settings
navigation:
title: "Configuration"
relatedPages: - path: /api-doc/config
title: Config Reference - path: /guide/guides/debugging
title: Debugging - path: /integrations/cli
title: CLI Integration
Most scans work without any config. But when you need control—excluding paths, adjusting concurrency, setting up auth—here's how.
The Config File
Create unlighthouse.config.ts in your project root:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
exclude: ['/admin/*', '/api/*'],
},
})Now run unlighthouse without flags. It picks up your config automatically.
::tip
The import is optional—skip it if you have resolution issues. The config still works.
::
Config File Location
Unlighthouse looks for these files in order:
unlighthouse.config.tsunlighthouse.config.jsunlighthouse.config.mjs
Or specify one explicitly:
unlighthouse --config-file ./configs/production.config.tsCommon Configs
Skip Paths You Don't Care About
export default defineUnlighthouseConfig({
site: 'https://example.com',
scanner: {
exclude: [
'/api/**', // API routes
'/admin/**', // Admin pages
'/amp/**', // AMP versions
],
},
})Or scan only specific paths:
export default defineUnlighthouseConfig({
scanner: {
include: ['/', '/products/**', '/blog/**'], // Only these, keep '/' so the crawler can start
},
})Desktop Instead of Mobile
Default is mobile. Switch to desktop:
export default defineUnlighthouseConfig({
scanner: {
device: 'desktop',
},
})More Accurate Scores
Lighthouse scores vary between runs. For reliable numbers:
export default defineUnlighthouseConfig({
scanner: {
samples: 3, // Run 3x per page, average the results
throttle: true, // Simulate real network (slower but realistic)
},
puppeteerClusterOptions: {
maxConcurrency: 1, // One at a time (less CPU contention)
},
})Trade-off: Much slower scans. Use for CI assertions, not development.
Protected Sites (Auth)
export default defineUnlighthouseConfig({
// Basic auth
auth: {
username: process.env.AUTH_USER,
password: process.env.AUTH_PASS,
},
// Cookie auth
cookies: [
{ name: 'session', value: process.env.SESSION_TOKEN, domain: '.example.com' },
],
})See full authentication guide.
CI Performance Budgets
Fail your build if scores drop:
export default defineUnlighthouseConfig({
ci: {
budget: {
'performance': 80,
'accessibility': 90,
'best-practices': 80,
'seo': 90,
},
buildStatic: true, // Generate shareable HTML report
},
})Advanced
Customize Lighthouse Directly
Pass options straight to Lighthouse:
export default defineUnlighthouseConfig({
lighthouseOptions: {
onlyCategories: ['performance', 'accessibility'], // Skip SEO/best-practices
skipAudits: ['uses-http2'], // Ignore specific audits
throttlingMethod: 'devtools',
},
})React to Scan Events
export default defineUnlighthouseConfig({
hooks: {
'task-complete': (path, report, taskName) => {
// fires for each task; only the Lighthouse task has scores
if (taskName !== 'runLighthouseTask')
return
const performance = report.report?.categories.find(category => category.key === 'performance')
if (performance?.score != null && performance.score < 0.5)
console.warn(`⚠️ ${path} scored ${performance.score}`)
},
'worker-finished': () => {
console.log('All done!')
},
},
})Environment-Based Config
export default defineUnlighthouseConfig(() => {
const isProd = process.env.NODE_ENV === 'production'
return {
site: isProd ? 'https://mysite.com' : 'http://localhost:3000',
scanner: {
samples: isProd ? 3 : 1,
throttle: isProd,
},
}
})Full Reference
See Config Reference for every option with types and defaults.
1.Guide Guides 1.Debugging (`docs/1.guide/guides/1.debugging.md`)
title: "Debugging Lighthouse Scans"
keywords:
- lighthouse debug
- lighthouse troubleshooting
- lighthouse not working
- puppeteer debug
- lighthouse scan failing
navigation:
title: "Debugging"
relatedPages: - path: /guide/guides/common-errors
title: Common Errors - path: /guide/guides/config
title: Configuration - path: /guide/guides/puppeteer
title: Puppeteer Configuration
Scan not working? Here's how to figure out why.
Start Here: Debug Logs
90% of issues become obvious with debug logging:
unlighthouse --site example.com --debugThis shows you:
- What URLs are being discovered
- Which pages are being scanned
- Any errors or timeouts
- Chrome connection status
Watch the Browser
Can't tell what's happening? Watch it:
export default defineUnlighthouseConfig({
puppeteerOptions: {
headless: false, // Show the browser window
slowMo: 250, // Slow down so you can see what's happening
devtools: true, // Open Chrome DevTools
},
puppeteerClusterOptions: {
maxConcurrency: 1, // One page at a time
},
})Now you'll see exactly what Unlighthouse sees—useful for:
- Auth issues (is login working?)
- JavaScript errors (check the console)
- Missing content (is the page rendering?)
- Blocked requests (check Network tab)
::warning
Visual mode is slow. Only use it to diagnose specific problems.
::
Common Issues
Pages Timing Out
Your site might be slow or have heavy JavaScript:
export default defineUnlighthouseConfig({
lighthouseOptions: {
maxWaitForLoad: 60000, // Wait up to 60s (default: 45s)
},
})SPA Not Rendering
JavaScript apps need time to hydrate:
export default defineUnlighthouseConfig({
scanner: {
skipJavascript: false, // Wait for JS to execute
},
lighthouseOptions: {
maxWaitForLoad: 45000,
},
})SSL/Certificate Errors
On localhost or staging with self-signed certs:
export default defineUnlighthouseConfig({
puppeteerOptions: {
ignoreHTTPSErrors: true,
args: ['--ignore-certificate-errors'],
},
})Chrome Won't Start
Try using bundled Chromium instead of system Chrome:
export default defineUnlighthouseConfig({
chrome: {
useSystem: false, // Download and use bundled Chromium
},
})Or point to a specific Chrome:
export default defineUnlighthouseConfig({
puppeteerOptions: {
executablePath: '/usr/bin/google-chrome-stable',
},
})Failed Network Requests
Log all failed requests to find what's breaking:
export default defineUnlighthouseConfig({
hooks: {
'puppeteer:before-goto': async (page) => {
page.on('requestfailed', (req) => {
console.log('❌ Failed:', req.url(), req.failure()?.errorText)
})
},
},
})Still Stuck?
- Check Common Errors for known issues
- Search GitHub Issues
- Ask on Discord with your debug logs
1.Guide Guides Authentication (`docs/1.guide/guides/authentication.md`)
title: "Authentication for Lighthouse Scans"
keywords:
- lighthouse authentication
- lighthouse password protected
- lighthouse login
- puppeteer authentication
- lighthouse behind login
- scan authenticated pages
navigation:
title: "Authentication"
relatedPages: - path: /guide/guides/debugging
title: Debugging - path: /guide/guides/puppeteer
title: Puppeteer Configuration - path: /guide/guides/config
title: Configuration
Need to scan pages behind a login? Unlighthouse supports every common auth pattern. Find yours below.
Quick Reference
| Auth Type | Best For | Config Key |
|---|---|---|
| Basic Auth | Staging environments with HTTP basic auth | auth |
| Cookies | Session tokens, JWTs in cookies | cookies |
| Headers | Bearer tokens, API keys | extraHeaders |
| localStorage | SPAs storing tokens in localStorage | localStorage |
| Programmatic | Complex login flows, 2FA | hooks.authenticate |
Basic Auth
For sites using HTTP Basic Authentication (the browser popup):
export default defineUnlighthouseConfig({
auth: {
username: process.env.AUTH_USER,
password: process.env.AUTH_PASS,
},
})Or via CLI:
unlighthouse --site staging.example.com --auth admin:secretpassCookies
Most common for session-based auth. Grab your session cookie from browser DevTools (Application → Cookies):
export default defineUnlighthouseConfig({
cookies: [
{
name: 'session_id',
value: 'abc123...',
domain: 'example.com', // Must match your site
path: '/',
},
],
})Getting the cookie value:
- Log into your site in Chrome
- Open DevTools → Application → Cookies
- Copy the session cookie value
- Paste into config (or use environment variable)
CLI shorthand:
unlighthouse --site example.com --cookies "session_id=abc123"
# Multiple cookies
unlighthouse --site example.com --cookies "session_id=abc123;csrf_token=xyz789"Headers (Bearer Tokens, API Keys)
For APIs or sites expecting Authorization headers:
export default defineUnlighthouseConfig({
extraHeaders: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
})CLI:
unlighthouse --site api.example.com --extra-headers "Authorization:Bearer abc123"Query Params
Some staging environments use URL tokens:
export default defineUnlighthouseConfig({
defaultQueryParams: {
access_token: process.env.STAGING_TOKEN,
},
})Every scanned URL will include ?access_token=...
localStorage (SPAs)
For React/Vue/Angular apps storing auth tokens in localStorage:
export default defineUnlighthouseConfig({
localStorage: {
auth_token: process.env.AUTH_TOKEN,
user_id: '12345',
},
})Unlighthouse sets these before each page loads.
Programmatic Login (Complex Flows)
For login forms, OAuth flows, or anything the simpler methods can't handle:
export default defineUnlighthouseConfig({
hooks: {
// the hook receives the Puppeteer page itself, not an object
async authenticate(page) {
// Navigate to login
await page.goto('https://example.com/login')
// Fill form
await page.type('input[name="email"]', '[email protected]')
await page.type('input[name="password"]', process.env.PASSWORD)
// Submit and wait for redirect
await Promise.all([
page.click('button[type="submit"]'),
page.waitForNavigation(),
])
},
},
})This runs once before scanning starts. The session persists for all pages.
Auth Not Sticking?
If authentication isn't persisting between page scans:
export default defineUnlighthouseConfig({
puppeteerOptions: {
userDataDir: './.unlighthouse-session', // Persist browser data
},
lighthouseOptions: {
disableStorageReset: true, // Don't clear storage between pages
skipAboutBlank: true,
},
})Debugging Auth Issues
Can't tell if auth is working? Watch it happen:
export default defineUnlighthouseConfig({
debug: true,
puppeteerOptions: {
headless: false, // See the browser
slowMo: 100, // Slow it down
},
puppeteerClusterOptions: {
maxConcurrency: 1, // One at a time
},
})Now you can watch the browser and see exactly where auth fails.
See the Debugging Guide for more techniques.
1.Guide Guides Chrome Dependency (`docs/1.guide/guides/chrome-dependency.md`)
title: "Chrome Dependency"
keywords:
- lighthouse chrome
- puppeteer chrome
- chromium lighthouse
- chrome executable path
- lighthouse browser
navigation:
title: "Chrome Dependency"
relatedPages: - path: /guide/guides/puppeteer
title: Puppeteer Configuration - path: /guide/guides/common-errors
title: Common Errors - path: /guide/guides/docker
title: Docker
Unlighthouse uses your system Chrome installation to keep package size minimal. When Chrome isn't available, it automatically downloads a compatible Chromium binary.
Disabling system Chrome
You can disable the system chrome usage by modifying the chrome.useSystem flag.
This will make Unlighthouse download and use the latest Chrome binary instead.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
chrome: {
useSystem: false
},
})Customizing the fallback installer
When Chrome can't be found on your system or if the chrome.useSystem: false flag is passed, then a fallback will be attempted.
This fallback will download a chrome binary for your system and use that path.
There are a number of options you can customize on this.
chrome.useDownloadFallback- Disables the fallback installerchrome.downloadFallbackVersion- Which Chrome for Testing version to download (default: the version pinned by the installedpuppeteer-core)chrome.downloadFallbackCacheDir- Where the binary should be saved (default$home/.unlighthouse)
export default defineUnlighthouseConfig({
chrome: {
useDownloadFallback: true,
downloadFallbackVersion: '153.0.8010.36',
downloadFallbackCacheDir: '/tmp/unlighthouse',
},
})Using your own chrome path
You can provide your own chrome path by setting puppeteerOptions.executablePath.
export default defineUnlighthouseConfig({
puppeteerOptions: {
executablePath: '/usr/bin/chrome',
},
})1.Guide Guides Common Errors (`docs/1.guide/guides/common-errors.md`)
title: "Common Errors"
keywords:
- lighthouse error
- lighthouse not working
- puppeteer error
- chrome connection refused
- wsl lighthouse
- lighthouse troubleshooting
- stop unlighthouse
- ctrl c not working git bash
navigation:
title: "Common Errors"
relatedPages: - path: /guide/guides/debugging
title: Debugging - path: /guide/guides/chrome-dependency
title: Chrome Dependency - path: /guide/guides/puppeteer
title: Puppeteer Configuration
Solutions for frequently encountered issues when running Unlighthouse scans. Ensure you're using the latest version before troubleshooting.
::tip
For general debugging techniques, see the Debugging Guide.
::
`connect ECONNREFUSED 127.0.0.1:`
Example
Error: Unable to launch browser for worker, error message: connect ECONNREFUSED 127.0.0.1:51667
This error is thrown when Chromium is unable to launch. This happens when puppeteer is unable to connect to the browser.
This can be from a number of reasons:
- The environment is not configured correctly, likely when using Windows and WSL.
- You have a firewall or antivirus blocking Chrome or Chromium from launching or connecting to the required port.
- You are using an unsupported version of Chrome or Chromium.
Windows and WSL Solution
- Install Puppeteer on WSL following the documentation.
- Install Chrome in WSL following the documentation.
Other Environments
- You can try disabling the system Chrome, instead using the fallback.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
chrome: {
useSystem: false,
},
})Can't stop the scan with `Ctrl+C`
Unlighthouse keeps running after a scan so you can browse the dashboard. Press Ctrl+C in the terminal to stop it; this shuts down the dev server and closes Chrome.
If Ctrl+C does nothing, you are almost certainly running inside Git Bash (MinTTY) on Windows. MinTTY does not forward Ctrl+C to native console programs such as Node, so the process keeps running and you have to kill it from Task Manager.
Solutions
- Wrap the command with winpty, which ships with Git for Windows:
winpty npx unlighthouse --site example.com- Or run the command from PowerShell, Command Prompt, or Windows Terminal instead of Git Bash, where
Ctrl+Cworks as expected.
1.Guide Guides Device (`docs/1.guide/guides/device.md`)
title: "Unlighthouse --desktop Flag & Device Configuration"
keywords:
- unlighthouse --desktop
- unlighthouse --desktop flag
- unlighthouse cli --desktop flag
- unlighthouse cli desktop option
- unlighthouse cli desktop flag
- unlighthouse --device desktop
- unlighthouse cli device desktop option
- lighthouse mobile
- lighthouse desktop
- lighthouse device emulation
- lighthouse viewport
- lighthouse throttling
- mobile performance testing
navigation:
title: "Device Configuration"
relatedPages: - path: /guide/guides/lighthouse
title: Lighthouse Configuration - path: /api-doc/config
title: Config Reference - path: /glossary
title: Core Web Vitals Glossary
Run Unlighthouse in desktop mode with the --desktop flag:
npx unlighthouse --site https://example.com --desktopThis overrides the default mobile emulation and scans every page using a desktop viewport. Prefer a config file? Set scanner.device: 'desktop' instead.
When to use `--desktop`
Mobile is the default because Google uses mobile-first indexing. But desktop scans still matter for:
- B2B SaaS dashboards (95%+ desktop traffic)
- Admin panels and internal tools
- Documentation sites
- Benchmarking against PageSpeed Insights desktop scores
The --desktop flag is equivalent to the --device desktop long form and takes precedence over any config file setting.
Device Types
Desktop Scanning
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
device: 'desktop',
},
})Mobile Scanning (Default)
export default defineUnlighthouseConfig({
scanner: {
device: 'mobile',
},
})Custom Dimensions
Test specific viewport sizes for responsive breakpoints:
export default defineUnlighthouseConfig({
lighthouseOptions: {
screenEmulation: {
width: 1800,
height: 1000,
},
},
})Network Throttling
Throttling simulates slower network and CPU conditions for more realistic performance testing:
export default defineUnlighthouseConfig({
scanner: {
throttle: true,
},
})::note
Throttling is enabled by default for remote sites and disabled for local sites, such as localhost. Set scanner.throttle to true or false to override it.
::
1.Guide Guides Docker (`docs/1.guide/guides/docker.md`)
title: "Run Lighthouse in Docker"
keywords:
- lighthouse docker
- docker lighthouse
- lighthouse container
- puppeteer docker
- lighthouse ci docker
navigation:
title: "Docker"
relatedPages: - path: /integrations/ci
title: CI Integration - path: /guide/guides/puppeteer
title: Puppeteer Configuration - path: /guide/guides/chrome-dependency
title: Chrome Dependency
Run Unlighthouse in Docker containers for consistent CI/CD environments. Docker requires special Puppeteer configuration due to sandboxing restrictions.
::warning
Docker support is community-maintained and experimental. Use the CI integration for best results.
::
Unlighthouse Config
It's recommended you only use the @unlighthouse/ci with Docker. Hosting the client does not have known support.
You will need to remove the Chrome sandbox in a Docker environment, this will require using an unlighthouse.config.ts file.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
puppeteerOptions: {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu',
'--ignore-certificate-errors',
],
},
})If you're using the unlighthouse binary instead of the CI integration, then you will need to tell Unlighthouse not to use the server and close when
the reports are finished.
export default defineUnlighthouseConfig({
server: {
open: false,
},
hooks: {
'worker-finished': async () => {
process.exit(0)
},
},
})Docker File
Please see the following community repos:
1.Guide Guides Dynamic Sampling (`docs/1.guide/guides/dynamic-sampling.md`)
title: "Dynamic Sampling"
keywords:
- lighthouse sampling
- scan subset of pages
- lighthouse large site
- reduce lighthouse scan time
- lighthouse scan optimization
navigation:
title: "Dynamic Sampling"
relatedPages: - path: /guide/guides/route-definitions
title: Route Definitions - path: /guide/recipes/large-sites
title: Large Sites - path: /guide/guides/url-discovery
title: URL Discovery
Automatically group similar pages and scan only representative samples. This significantly reduces scan time for sites with many similar URLs like blogs, e-commerce product pages, or documentation sites.
How it works
When dynamic sampling is enabled, it will group paths into chunks based on their path tree.
For example, let's imagine we have a blog on our site and there are hundreds of blog posts. Scanning every blog post will
take a long time and may even break Unlighthouse.
The path structure is /blog/{post}.
Unlighthouse will turn this path structure into groups based on the /blog prefix. By default, it will sample
5 paths starting with this prefix.
A sample being a random selection of paths within this group.
For example if we have the posts:
/blog/post-a/blog/post-b/blog/post-c/blog/post-d/blog/post-e/blog/post-f/blog/post-g/blog/post-h/blog/post-i
After sampling, we may end up with the random selection:
/blog/post-c/blog/post-d/blog/post-e/blog/post-h/blog/post-i
Usage
It is configured using the scanner.dynamicSampling option.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
dynamicSampling: 10, // Number of samples per group (default: 8)
},
})Disable Dynamic Sampling
export default defineUnlighthouseConfig({
scanner: {
dynamicSampling: false,
},
})Alternatively, you can disable it using the CLI --disable-dynamic-sampling.
1.Guide Guides Generating Static Reports (`docs/1.guide/guides/generating-static-reports.md`)
title: "Generate Lighthouse Reports"
keywords:
- lighthouse report
- lighthouse html report
- lighthouse json report
- lighthouse csv export
- static lighthouse report
- lighthouse report generation
- bulk lighthouse report
navigation:
title: "Static Reports"
relatedPages: - path: /integrations/ci
title: CI Integration - path: /guide/guides/config
title: Configuration - path: /guide/recipes/client
title: UI Customization
Create static, shareable Lighthouse reports for your entire site. Export as interactive HTML dashboards, CSV for spreadsheet analysis, or JSON for CI/CD integration.
Report Types
| Format | Use Case | Command |
|---|---|---|
| HTML | Shareable dashboards | --build-static |
| JSON | CI/CD integration | --reporter json |
| CSV | Spreadsheet analysis | --reporter csv |
| LHCI | Server upload | --reporter lighthouseServer |
Unlike single-page Lighthouse reports, Unlighthouse aggregates results across your entire site.
::tip
Static reports are perfect for stakeholder reviews, automated deployments, and long-term performance tracking.
::
Installation
Install the CLI globally to use unlighthouse-ci:
::code-group
npm install @unlighthouse/cliyarn global add @unlighthouse/clipnpm install @unlighthouse/cli::
For complete CI features, see the CI Integration Guide.
HTML Reports
You can create static, self-hosted reports for your sites using the CI. This allows you to generate an always up-to-date version
of how your site is performing overall.
You can see an example of this here: https://inspect.unlighthouse.dev/.
You can generate a report like this by providing the --build-static flag.
unlighthouse-ci --site <your-site> --build-staticThis will generate files in your outputPath (.unlighthouse by default). Upload that whole folder to a static host; index.html sits at its root.
If you want to preview the static report you can run npx sirv-cli .unlighthouse
Note: You will need to host your site using a web server.
CloudFlare Pages Example
You should create a CloudFlare Pages site using Direct Upload.
You will use the wrangler CLI to upload the static report.
You will need to init wrangler and configure it for your requirements.
wrangler initYou can then run the following command to generate the static report and upload it to CloudFlare Pages.
unlighthouse-ci --site www.example.com --build-static && wrangler pages publish .unlighthouseGitHub Actions & Netlify Example
This example is for GitHub Actions and deploys a static client build to Netlify.
name: Assertions and static report
on:
workflow_dispatch:
jobs:
demo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Dependencies
run: npm add @unlighthouse/cli
- name: Unlighthouse assertions and client
run: unlighthouse-ci --site <your-site> --build-static
- name: Deploy report to Netlify
uses: nwtgck/[email protected]
with:
publish-dir: ./.unlighthouse
production-branch: main
production-deploy: true
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: New Release Deploy from GitHub Actions
enable-pull-request-comment: false
enable-commit-comment: true
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
timeout-minutes: 1CSV Reports
You can generate a CSV report by providing the --reporter csv or --reporter csvExpanded flag.
unlighthouse-ci --site <your-site> --reporter csvNote: This report format is experimental and may change in the future.
This will generate a report like the following (csvExpanded sample):
URL,Score,Performance,Accessibility,Best Practices,SEO,Largest Contentful Paint,Cumulative Layout Shift,FID,Blocking,Color Contrast,Headings,Image Alts,Link Names,Errors,Inspector Issues,Images Responsive,Image Aspect Ratio,Indexable,Tap Targets
"/",93,72,100,100,100,3211.44,0,290.36,562.29,1,1,1,1,1,1,1,1,1,1
"/blog",94,77,100,100,100,3911.7,0,205.57,260.03,1,1,1,1,1,1,1,1,1,1
"/blog/2023-april",88,51,100,100,100,4925.84,0,311.24,797.95,1,1,1,1,1,1,1,1,1,1
"/blog/2023-february",96,82,100,100,100,3207.98,0,209.96,302.01,1,1,1,1,1,1,1,1,1,1
"/blog/nuxt-3-migration-cheatsheet",85,43,97,100,100,4373.88,0,1581.52,2820.75,0,1,1,1,1,1,1,1,1,1
"/blog/vue-automatic-component-imports",93,74,97,100,100,1696.51,0,793.36,1314.27,0,1,1,1,1,1,1,1,1,1
"/blog/vue-use-head-v1",82,30,97,100,100,8053.8,0,379.59,1127.99,0,1,1,1,1,1,1,1,1,1
"/projects",92,66,100,100,100,3666.86,0,322.48,625.51,1,1,1,1,1,1,1,1,1,1
"/sponsors",92,69,100,100,100,4438.15,0,362.62,408.63,1,1,1,1,1,1,1,1,1,1
"/talks",98,90,100,100,100,864.86,0,390.93,427.94,1,1,1,1,1,1,1,1,1,1JSON Reports
You can generate a JSON report by providing the --reporter json or --reporter jsonExpanded flag.
unlighthouse-ci --site <your-site> --reporter jsonNote: This report format is experimental and may change in the future.
This will generate a report like the following (json sample):
/* Detailed source-code truncated for AI context efficiency. */LHCI Reports
You can upload your reports to a Lighthouse CI server using the lighthouseServer reporter.
You will need to provide the --lhci-host and --lhci-build-token flags.
unlighthouse-ci --site <your-site> --reporter lighthouseServer --lhci-host <lhci host> --lhci-build-token <lhci buildToken>This will upload your reports to the Lighthouse CI server.
Basic Auth
You can provide basic auth credentials using the --lhci-auth flag.
unlighthouse-ci --site <your-site> --reporter lighthouseServer --lhci-host <lhci host> --lhci-build-token <lhci buildToken> --lhci-auth <username>:<password>1.Guide Guides Lighthouse (`docs/1.guide/guides/lighthouse.md`)
title: "Lighthouse Configuration"
keywords:
- lighthouse options
- lighthouse configuration
- lighthouse categories
- customize lighthouse
- lighthouse audit settings
navigation:
title: "Lighthouse Config"
relatedPages: - path: /guide/guides/device
title: Device Configuration - path: /api-doc/config
title: Config Reference - path: /glossary
title: Core Web Vitals Glossary
Customize audit categories, performance thresholds, and behavior through the lighthouseOptions configuration key. Unlighthouse passes these options directly to Google Lighthouse.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
lighthouseOptions: {
throttlingMethod: 'devtools',
},
})For complete options, see the Lighthouse Configuration docs.
Aliases
Unlighthouse aims to minimise and simplify configuration, where possible.
For this reason, a number of configurations aliases are provided for your convenience.
You can always configure lighthouse directly if you are comfortable with the configuration.
Selecting Categories
By default, Unlighthouse will scan the categories: 'performance', 'accessibility', 'best-practices', 'seo'.
The performance category measures Core Web Vitals including LCP, CLS, and INP.
It can be useful to remove certain categories from being scanned to improve scan times. The Unlighthouse UI will adapt
to any categories you select.
Only Performance
export default defineUnlighthouseConfig({
lighthouseOptions: {
onlyCategories: ['performance'],
},
})1.Guide Guides Puppeteer (`docs/1.guide/guides/puppeteer.md`)
title: "Puppeteer Launch Options"
keywords:
- puppeteer launch options
- puppeteer.launch options
- puppeteer args
- puppeteer headless
- puppeteer chrome flags
- lighthouse puppeteer
- puppeteer evaluateonnewdocument localstorage
- puppeteer.evaluateonnewdocument
- puppeteer set localstorage
navigation:
title: "Puppeteer"
relatedPages: - path: /guide/guides/chrome-dependency
title: Chrome Dependency - path: /guide/guides/authentication
title: Authentication - path: /guide/guides/docker
title: Docker
Unlighthouse uses Puppeteer to control Chrome for Lighthouse audits. Configure browser behavior, navigation hooks, and Chrome flags via puppeteerOptions.
All Available Options
export default defineUnlighthouseConfig({
puppeteerOptions: {
// See: https://pptr.dev/api/puppeteer.launchoptions
},
})Full reference: Puppeteer LaunchOptions API
Common Configurations
Headless Mode
// Run with visible browser (debugging)
export default defineUnlighthouseConfig({
puppeteerOptions: {
headless: false,
},
})Custom Chrome Executable
export default defineUnlighthouseConfig({
puppeteerOptions: {
executablePath: '/usr/bin/google-chrome',
},
})Chrome Arguments
Common args for CI/Docker environments:
export default defineUnlighthouseConfig({
puppeteerOptions: {
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
},
})Viewport Settings
export default defineUnlighthouseConfig({
puppeteerOptions: {
defaultViewport: {
width: 1920,
height: 1080,
},
},
})Timeout Configuration
export default defineUnlighthouseConfig({
puppeteerOptions: {
timeout: 60000, // 60 seconds
},
})User Data Directory
Persist browser data between runs:
export default defineUnlighthouseConfig({
puppeteerOptions: {
userDataDir: './.puppeteer-data',
},
})Navigation Hooks
Hook into Puppeteer's page navigation for custom logic.
Before Page Load
export default defineUnlighthouseConfig({
hooks: {
'puppeteer:before-goto': async (page) => {
// Set localStorage before navigation
await page.evaluateOnNewDocument((token) => {
localStorage.setItem('auth', token)
}, process.env.AUTH_TOKEN)
},
},
})Modify Page Content
export default defineUnlighthouseConfig({
hooks: {
'puppeteer:before-goto': async (page) => {
page.waitForNavigation().then(async () => {
// Remove elements that cause CLS
await page.evaluate(() => {
document.querySelector('.cookie-banner')?.remove()
})
})
},
},
})Troubleshooting
Chrome not found
Connection refused in Docker
Add --no-sandbox and --disable-dev-shm-usage args.
Memory issues
Reduce concurrent workers or add --disable-dev-shm-usage.
1.Guide Guides Route Definitions (`docs/1.guide/guides/route-definitions.md`)
title: "Route Definitions"
keywords:
- lighthouse routes
- page discovery
- custom sampling
- route mapping
- lighthouse url mapping
navigation:
title: "Route Definitions"
relatedPages: - path: /guide/guides/dynamic-sampling
title: Dynamic Sampling - path: /guide/guides/url-discovery
title: URL Discovery - path: /guide/guides/config
title: Configuration
Map URLs to source files for intelligent dynamic sampling. Framework integrations discover routes automatically; CLI users may need manual configuration for optimal sampling.
Pages directory
By default, the pages/ dir is scanned for files with extensions .vue and .md, from the root directory.
If your project has a different setup you can modify the configuration.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
root: './app',
discovery: {
pagesDir: 'routes',
fileExtensions: ['jsx', 'md'],
},
})Custom sampling
When you have URL patterns which don't use URL segments or the mapping is failing, it can be useful to map the sampling
yourself.
By using the customSampling option you map regex to a route definition.
In the below example we will map any URL such as /q-search-query, /q-where-is-the-thing to a single route
definition, which allows the sampling to work.
export default defineUnlighthouseConfig({
scanner: {
customSampling: {
'/q-(.*?)': {
name: 'search-query',
},
},
},
})1.Guide Guides Url Discovery (`docs/1.guide/guides/url-discovery.md`)
title: "URL Discovery"
keywords:
- lighthouse sitemap
- crawl website lighthouse
- lighthouse all pages
- automatic url discovery
- lighthouse find all pages
navigation:
title: "URL Discovery"
relatedPages: - path: /guide/guides/route-definitions
title: Route Definitions - path: /guide/recipes/large-sites
title: Large Sites - path: /guide/guides/config
title: Configuration
Unlighthouse automatically finds all pages on your site using multiple discovery methods. Configure which sources to use and filter results to scan exactly what you need.
Unlighthouse discovers URLs through multiple methods:
- Add the specified
sitefrom--siteor config - Manually providing URLs via the
--urlsflag orurlson the provider. robotsTxt- Reading robots.txt, if it exists. Provides sitemap URLs and disallowed paths.sitemap- Reading sitemap.xml, if it existscrawler- Inspecting internal links- Using provided static route definitions
Robots.txt
When a robots.txt is found, it will attempt to read the sitemap and disallowed paths.
Disabling robots
You may not want to use the robots.txt in all occasions. For example if you want to scan
URLs which are disallowed.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
// disable robots.txt scanning
robotsTxt: false,
},
})Sitemap.xml
By default, the sitemap config will be read from your /robots.txt. Otherwise, it will fall back to using /sitemap.xml.
Note: When a sitemap exists with over 50 paths, it will disable the crawler.
Manual sitemap paths
You may provide an array of sitemap paths to scan.
export default defineUnlighthouseConfig({
scanner: {
sitemap: [
'/sitemap.xml',
'/sitemap2.xml',
],
},
})Disabling scan
If you know your site doesn't have a sitemap, it may make sense to disable it.
export default defineUnlighthouseConfig({
scanner: {
// disable sitemap scanning
sitemap: false,
},
})Crawler
When enabled, the crawler will inspect the HTML payload of a page and extract internal links.
These internal links will be queued up and scanned if they haven't already been scanned.
Disable crawling
If you have many pages with many internal links, it may be a good idea to disable the crawling.
export default defineUnlighthouseConfig({
scanner: {
crawler: false,
},
})Manually Providing URLs
While not recommended for most use cases, you may provide relative URLs within your configuration file, or use the --urls flag.
This will disable the crawler and sitemap scanning.
Can be provided statically.
export default defineUnlighthouseConfig({
urls: [
'/about',
'/other-page',
],
})Or you can return a function or promise.
export default defineUnlighthouseConfig({
urls: async () => await getUrls(),
})Specify explicit relative URLs as a comma-separated list.
unlighthouse --site https://example.com --urls /about,/other-page1.Guide Recipes Client (`docs/1.guide/recipes/client.md`)
title: "Customizing the UI"
keywords:
- lighthouse dashboard
- lighthouse ui
- custom lighthouse metrics
- lighthouse report customization
- lighthouse custom columns
navigation:
title: "UI Customization"
relatedPages: - path: /api-doc/glossary
title: Glossary - path: /guide/guides/config
title: Configuration - path: /guide/guides/generating-static-reports
title: Static Reports
Customize the Unlighthouse dashboard to display metrics relevant to your workflow. Replace default columns, add custom audit displays, and configure how results appear.
Customizing Columns
Replace or add columns to display specific Lighthouse metrics:
Example: Replace FCP with Server Response Time
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
hooks: {
'resolved-config': function (config) {
config.client.columns.performance[2] = {
cols: 1,
label: 'Response Time',
tooltip: 'Time for the server to respond',
sortKey: 'numericValue',
key: 'report.audits.server-response-time',
}
},
},
})::tip
See the Column API Reference for all available column options.
::
1.Guide Recipes Improving Accuracy (`docs/1.guide/recipes/improving-accuracy.md`)
title: "Improving Lighthouse Accuracy"
keywords:
- lighthouse accuracy
- lighthouse score variability
- lighthouse consistent results
- lighthouse multiple runs
- core web vitals accuracy
- lighthouse reliable scores
navigation:
title: "Improving Accuracy"
relatedPages: - path: /guide/guides/config
title: Configuration - path: /guide/guides/device
title: Device Configuration - path: /glossary
title: Core Web Vitals Glossary
Lighthouse scores can vary 5-10 points between runs due to network conditions, CPU load, and browser state. These techniques improve consistency for reliable Core Web Vitals measurement.
Why Scores Vary
Single Lighthouse runs can fluctuate by 5-10 points due to:
- CPU load from other browser tabs or processes
- Network latency variations
- Memory pressure
- Background service workers
For reliable performance monitoring, use multiple samples.
Multiple Samples Per URL
Run Lighthouse multiple times and average the results for better accuracy:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
samples: 3, // Run 3 scans per URL and average results
},
})::tip
Use samples: 3 for development, samples: 5 for CI/production audits.
::
Reduce Parallel Scans
Limit concurrent workers to reduce CPU contention and improve score consistency:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
puppeteerClusterOptions: {
maxConcurrency: 1, // Single worker for maximum accuracy
},
})Enable Throttling
Network throttling simulates real-world conditions and reduces score variability:
export default defineUnlighthouseConfig({
scanner: {
throttle: true, // Simulate 4G network
},
})Recommended Production Config
For the most accurate results:
export default defineUnlighthouseConfig({
scanner: {
samples: 5,
throttle: true,
},
puppeteerClusterOptions: {
maxConcurrency: 1,
},
})::warning
Higher accuracy increases scan time significantly. Balance accuracy needs with scan duration.
::
1.Guide Recipes Large Sites (`docs/1.guide/recipes/large-sites.md`)
title: "Bulk Lighthouse Testing for Large Sites"
keywords:
- bulk lighthouse test
- scan entire website lighthouse
- lighthouse all pages
- site wide lighthouse
- batch lighthouse
- lighthouse thousands of pages
- enterprise lighthouse
navigation:
title: "Large Sites"
relatedPages: - path: /guide/guides/dynamic-sampling
title: Dynamic Sampling - path: /guide/guides/url-discovery
title: URL Discovery - path: /guide/guides/route-definitions
title: Route Definitions
Scan websites with thousands of pages efficiently. Unlike single-page tools like PageSpeed Insights, Unlighthouse handles large sites with smart sampling, parallel scanning, and configurable limits.
- Automatic discovery - Finds all pages via sitemap and crawling
- Smart sampling - Tests representative pages from each template
- Parallel scanning - Multiple Chrome instances for speed
- Aggregated results - Site-wide scores and insights
Unlighthouse includes smart defaults for large sites. Understanding these helps balance completeness with performance.
Default Large Site Configuration
These defaults optimize scanning for sites with thousands of pages:
- ignoreI18nPages enabled
- maxRoutes set to 200
- skipJavascript enabled
- samples set to 1
- throttling enabled, except for local sites
- crawler enabled
- dynamicSampling set to 8
For example, when scanning a blog with thousands of posts, it may be redundant to scan every single blog post, as the
DOM is very similar. Using the configuration we can select exactly how many posts should be scanned.
Manually select URLs
You can configure Unlighthouse to use an explicit list of relative paths. This can be useful if you have a fairly complex
and large site.
See Manually providing URLs for more information.
Provide Route Definitions (optional)
To make the most intelligent sampling decisions, Unlighthouse needs to know which page files are available. When running
using the
integration API, Unlighthouse will automatically provide this information.
Using the CLI you should follow the providing route definitions guide.
Note: When no route definitions are provided it will match based on URL fragments, i.e /blog/post-slug-3 will be
mapped toblog-slug.
Exclude URL Patterns
Paths to ignore from scanning.
For example, if your site has a documentation section, that doesn't need to be scanned.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
exclude: [
'/docs/**',
],
},
})Include URL Patterns
Explicitly include paths; this will exclude any paths not listed here.
For example, if you run a blog and want to only scan your article and author pages.
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
include: [
'/', // the crawler starts here
'/articles/**',
'/authors/**',
],
},
})Change Dynamic Sampling Limit
By default, a URL will be matched to a specific route definition 8 times.
You can change the sample limit with:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
dynamicSampling: 20, // 20 samples per page template
},
})Disabling Sampling
In cases where the route definitions aren't provided, a less-smart sampling will occur where URLs under the same parent
will be sampled.
For these instances you may want to disable the sample as follows:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
dynamicSampling: false, // Disable sampling completely
},
})1.Guide Recipes Spa (`docs/1.guide/recipes/spa.md`)
title: "Single-Page Applications"
keywords:
- lighthouse spa
- lighthouse react
- lighthouse vue
- lighthouse angular
- lighthouse client side rendering
- spa performance testing
- lighthouse csr
navigation:
title: "SPAs"
relatedPages: - path: /guide/guides/url-discovery
title: URL Discovery - path: /guide/guides/puppeteer
title: Puppeteer Configuration - path: /glossary/lcp
title: LCP for SPAs
Scan React, Vue, Angular, and other SPAs with client-side routing. SPAs require JavaScript execution for link discovery and accurate Core Web Vitals measurement.
Enable JavaScript Execution
Allow Puppeteer to execute JavaScript before extracting page content:
import { defineUnlighthouseConfig } from 'unlighthouse/config'
export default defineUnlighthouseConfig({
scanner: {
skipJavascript: false, // Enable JS execution for SPAs
},
})Wait for Hydration
SPAs often need time to hydrate. Configure wait conditions:
export default defineUnlighthouseConfig({
scanner: {
skipJavascript: false,
},
lighthouseOptions: {
maxWaitForLoad: 45000, // Wait up to 45s for page load
},
})Provide URLs Manually
If automatic crawling misses routes, provide them explicitly:
export default defineUnlighthouseConfig({
urls: [
'/',
'/about',
'/products',
'/contact',
],
scanner: {
skipJavascript: false,
},
})SPA Performance Considerations
SPAs typically have worse LCP scores because:
- Content renders after JavaScript execution
- Initial HTML is often empty or minimal
- Hydration adds to INP delays
Consider SSR or SSG for content-heavy pages to improve Core Web Vitals.
::note
Enabling JavaScript execution increases scan time but is necessary for accurate SPA scanning.
::
--- METRICS ---
- Files Extracted: 47
- Estimated Token Budget: ~43338 tokens
- Canonical Reference: https://codewiki.google/github.com/harlan-zw/unlighthouse