# Technical Documentation: harlan-zw/unlighthouse

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/harlan-zw/unlighthouse)

### Overview (`README.md`)

<h1>Unlighthouse</h1>

[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
<a href="https://skilld.dev/gh/harlan-zw/unlighthouse">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://skilld.dev/b/harlan-zw/unlighthouse?theme=dark">
    <source media="(prefers-color-scheme: light)" srcset="https://skilld.dev/b/harlan-zw/unlighthouse?theme=light">
    <img alt="Skill repository on skilld.dev" src="https://skilld.dev/b/harlan-zw/unlighthouse?theme=light">
  </picture>
</a>

<p align="center">
Unlighthouse scans your entire site using Google Lighthouse,<br> with a modern UI, minimal config and smart sampling.
</p>

<p align="center"><a href="https://inspect.unlighthouse.dev/">View Demo</a></p>

<p align="center">
<table>
<tbody>
<td align="center">
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
</td>
</tbody>
</table>
</p>

## Quick Setup

Run the following command:

```bash
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](https://skilld.dev/gh/harlan-zw/unlighthouse).

_Requirements: Node >= 22.18.0._

## Getting Started

Install instructions for all integrations can be found on the [docs](https://unlighthouse.dev/) site.

Need a hand? Join the [Discord](https://discord.gg/275MBUBvgP) for one-on-one help.

### gitignore

Unlighthouse will save your reports in `outputDir`,
it's recommended you .gitignore these files.

```shell
.unlighthouse
```

#### Debugging

If you run into any issues with Unlighthouse, the first step should be to re-run the scan with debugging enabled.

```bash
# NPM
npx unlighthouse --site unlighthouse.dev --debug
# or PNPM
pnpm dlx unlighthouse --site unlighthouse.dev --debug
```

## Docs

Integration instructions, Guides, API and config spec can be found on [docs](https://unlighthouse.dev/) site.

## Sponsors

<p align="center">
  <a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
    <img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg' alt='Sponsors'/>
  </a>
</p>

## License

Licensed under the [MIT license](https://github.com/harlan-zw/unlighthouse/blob/main/LICENSE.md).


[npm-version-src]: https://img.shields.io/npm/v/unlighthouse/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-version-href]: https://npmjs.com/package/unlighthouse

[npm-downloads-src]: https://img.shields.io/npm/dm/unlighthouse.svg?style=flat&colorA=18181B&colorB=28CF8D
[npm-downloads-href]: https://npmjs.com/package/unlighthouse

[license-src]: https://img.shields.io/github/license/harlan-zw/unlighthouse.svg?style=flat&colorA=18181B&colorB=28CF8D
[license-href]: https://github.com/harlan-zw/unlighthouse/blob/main/LICENSE.md

---

### 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](https://github.com/harlan-zw/unlighthouse), enabling automated website scanning in development and deployment workflows.

## Installation

```bash
# Use directly with npx
npx unlighthouse --site https://example.com

# Or install globally
npm install @unlighthouse/cli
```

## Usage

### Basic Scanning

```bash
# 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 --mobile
```

### Advanced Options

```bash
# 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.ts
```

### CI Integration

```bash
# CI mode with exit codes for failed audits
unlighthouse-ci --site https://example.com --budget 75
```

## Configuration

Create `unlighthouse.config.ts` in your project root:

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  site: 'https://example.com',
  debug: true,
  scanner: {
    device: 'desktop',
    throttle: false,
  },
  lighthouseOptions: {
    onlyCategories: ['performance', 'accessibility'],
  }
})
```

## Documentation

- [CLI Integration Guide](https://unlighthouse.dev/integrations/cli.html)
- [CI Integration Guide](https://unlighthouse.dev/integrations/ci.html)
- [Configuration Reference](https://unlighthouse.dev/guide/config.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### Overview (`packages/client/README.md`)

# @unlighthouse/client

The Vue-based user interface for [Unlighthouse](https://github.com/harlan-zw/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

```bash
# Install dependencies
pnpm install

# Start development server
pnpm dev

# Build for production
pnpm build
```

## Documentation

- [Integration Guide](https://unlighthouse.dev/integrations/)
- [API Reference](https://unlighthouse.dev/api/index.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### Overview (`packages/core/README.md`)

# @unlighthouse/core

The core engine of [Unlighthouse](https://github.com/harlan-zw/unlighthouse) that handles website scanning, Lighthouse execution, and report generation.

## Usage

### Basic Usage

```ts
import { createUnlighthouse } from '@unlighthouse/core'

const unlighthouse = await createUnlighthouse({
  site: 'https://example.com',
  debug: true,
  scanner: {
    device: 'desktop',
  }
})

await unlighthouse.start()
```

### With Custom Provider

```ts
import { createUnlighthouse } from '@unlighthouse/core'

const unlighthouse = await createUnlighthouse(
  { /* user config */ },
  {
    name: 'custom',
    routeDefinitions: () => generateRouteDefinitions(),
  }
)
```

### Hooks

```ts
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 Unlighthouse
- `useUnlighthouse()` - Access the global Unlighthouse context
- `generateClient(options)` - Generate static client files

## Documentation

- [API Reference](https://unlighthouse.dev/api/index.html)
- [Configuration Guide](https://unlighthouse.dev/guide/config.html)
- [Puppeteer Configuration](https://unlighthouse.dev/guide/puppeteer.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### Overview (`packages/server/README.md`)

# @unlighthouse/server

The server package for [Unlighthouse](https://github.com/harlan-zw/unlighthouse) that provides the web server and API endpoints for the scanning interface.

## Usage

```ts
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

- [API Reference](https://unlighthouse.dev/api/index.html)
- [Configuration Guide](https://unlighthouse.dev/guide/config.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### Overview (`packages/unlighthouse-ci/README.md`)

# unlighthouse-ci

Dedicated CI package for [Unlighthouse](https://github.com/harlan-zw/unlighthouse) that provides continuous integration capabilities with budget enforcement and exit codes.

## Installation

```bash
# Use directly with npx
npx unlighthouse-ci --site https://example.com

# Or install globally
npm install unlighthouse-ci
```

## Usage

### Basic CI Scanning

```bash
# 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-reports
```

### GitHub Actions Integration

```yaml
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 80
```

### Features

- 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:

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  site: 'https://example.com',
  ci: {
    budget: 80,
    buildStatic: true,
  },
  lighthouseOptions: {
    onlyCategories: ['performance', 'accessibility', 'seo'],
  }
})
```

## Documentation

- [CI Integration Guide](https://unlighthouse.dev/integrations/ci.html)
- [GitHub Actions Example](https://unlighthouse.dev/integrations/ci.html#github-actions)
- [Configuration Reference](https://unlighthouse.dev/guide/config.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### Overview (`packages/unlighthouse/README.md`)

# unlighthouse

The main package for [Unlighthouse](https://github.com/harlan-zw/unlighthouse) - scan your entire website with Google Lighthouse. This is a convenience package that includes the core functionality and CLI tools.

## Quick Start

```bash
# Scan your website instantly
npx unlighthouse --site https://example.com

# CI mode with performance budgets
npx unlighthouse-ci --site https://example.com --budget 80
```

## What's Included

This package includes:
- `@unlighthouse/core` - Core scanning engine
- `@unlighthouse/cli` - Command-line interface
- `@unlighthouse/client` - Web interface for results
- Two binaries: `unlighthouse` and `unlighthouse-ci`

## Installation

```bash
# Global installation
npm install unlighthouse

# Project dependency
npm install unlighthouse --save-dev
```

## Usage

### Interactive CLI

```bash
# 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.ts
```

### Programmatic Usage

```ts
import { createUnlighthouse } from 'unlighthouse'

const unlighthouse = await createUnlighthouse({
  site: 'https://example.com',
  debug: true
})

await unlighthouse.start()
```

### CI Integration

```bash
# Enforce performance budgets in CI
unlighthouse-ci --site https://example.com --budget 85
```

## Configuration

Create `unlighthouse.config.ts`:

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  site: 'https://example.com',
  scanner: {
    device: 'desktop',
    throttle: false,
  },
  lighthouseOptions: {
    onlyCategories: ['performance', 'accessibility'],
  }
})
```

## Documentation

- [Getting Started Guide](https://unlighthouse.dev/guide/)
- [CLI Integration](https://unlighthouse.dev/integrations/cli.html)
- [CI Integration](https://unlighthouse.dev/integrations/ci.html)
- [API Reference](https://unlighthouse.dev/api/)
- [Configuration Reference](https://unlighthouse.dev/guide/config.html)

## License

MIT License © 2021-PRESENT [Harlan Wilton](https://github.com/harlan-zw)

---

### 3.Api Doc Index (`docs/3.api-doc/index.md`)

---
title: "API Reference"
description: "Complete API reference for Unlighthouse core functions, context methods, and hooks system for building custom integrations."
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<UnlighthouseContext>`{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.

  ```ts
  import { 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<UnlighthouseContext>`{lang="ts"}

  A simple define wrapper to provide typings to config definitions. This is primarily used when creating a
  config file `unlighthouse.config.ts`

  Powered by [c12](https://github.com/unjs/c12).

  ```ts
  import { 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/ui` to 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.

  ```ts
  import { 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](https://github.com/unjs/unctx) to retain the core state. This allows you to access unlighthouse _anywhere_,
  which is great to avoid transferring state between your logic.

  ```ts
  import { 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
  `debug` config.

  ```ts
  import { 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](https://github.com/unjs/h3) app which uses [listhen](https://github.com/unjs/listhen) as a web server.
  This is used to host the API and the client.

  ```ts
  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()
  ```

## Unlighthouse Context

Functions exposed from unlighthouse context provided by `useUnlighthouse()` or `createUnlighthouse()` .

### `start()`{lang="ts"}

- **Type:** `() => Promise<UnlighthouseContext>`{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<UnlighthouseContext>`{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<UnlighthouseContext>`{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](https://github.com/unjs/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`.

```ts
export type HookResult = Promise<void> | void
```

```ts
import { 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.

  ```ts
  hooks.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.

  ```ts
  hooks.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/ui` for 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"
description: "Complete guide to Core Web Vitals and Lighthouse metrics. Learn what LCP, CLS, and INP measure, their impact on SEO, and how to improve them."
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](/glossary/tbt) | 30% | Lab |
| [LCP](/glossary/lcp) | 25% | Core Web Vital |
| [CLS](/glossary/cls) | 25% | Core Web Vital |
| [FCP](/glossary/fcp) | 10% | Lab |
| [Speed Index](/glossary/speed-index) | 10% | Lab |

### Deprecated Metrics

- [TTI (Time to Interactive)](/glossary/tti) - Removed in Lighthouse 10. Use [TBT](/glossary/tbt) or [INP](/glossary/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](/guide/getting-started/unlighthouse-cli) to audit all these metrics across your entire website in a single scan:

```bash
npx unlighthouse --site https://example.com
```

Unlike 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?"
description: "Learn what Speed Index measures, its thresholds, and how to improve visual loading speed for better user experience."
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](https://github.com/paulirish/speedline) library to:

1. Calculate visual completeness at each frame
2. Compute the area under the visual progress curve
3. 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](/glossary/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](/glossary/fcp) | First content appears | Single moment |
| [LCP](/glossary/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](/guide/getting-started/unlighthouse-cli) to measure Speed Index across your entire site.

For individual pages:
- Lighthouse in Chrome DevTools
- [PageSpeed Insights](https://pagespeed.web.dev/)
- 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](https://developer.chrome.com/docs/lighthouse/performance/speed-index):

1. **Minimize main thread work** - Reduce JavaScript execution during load
2. **Reduce JavaScript execution time** - Optimize and split code
3. **Ensure text remains visible during webfont load** - Use `font-display: swap`

Additional strategies:

4. **Optimize critical rendering path** - Inline critical CSS, defer non-critical
5. **Optimize images** - Compress, use modern formats (WebP, AVIF), lazy-load below fold
6. **Prioritize visible content** - Load above-the-fold content first
7. **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](/glossary/tbt) | 30% |
| [LCP](/glossary/lcp) | 25% |
| [CLS](/glossary/cls) | 25% |
| [FCP](/glossary/fcp) | 10% |
| Speed Index | 10% |

::note
Speed Index is a lab metric, not a Core Web Vital. Focus on [LCP](/glossary/lcp), [CLS](/glossary/cls), and [INP](/glossary/inp) for search ranking impact.
::

---

### Integration Deprecations (`docs/integration-deprecations.md`)

---
title: "Integration Deprecations"
icon: carbon:warning-alt
description: "Build tool integrations are deprecated in v1.0. Learn about migration paths and alternatives."
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](/integrations/cli) or [CI](/integrations/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.

```bash
npx unlighthouse --site localhost:3000
```

The 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
description: "Scan your entire website with Lighthouse from the command line. Alternative to lighthouse CLI that audits all pages automatically."
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)

```bash
npm install unlighthouse
```

### One-time run (no install)

```bash
npx unlighthouse --site example.com
```

Unlighthouse wraps the [Lighthouse npm package](https://www.npmjs.com/package/lighthouse) 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.

```bash
unlighthouse --site example.com --debug
```

Run without caching, throttle the requests and do 3 samples.

```bash
unlighthouse --site example.com --debug --no-cache --throttle --samples 3
```

## Configuration

Configuring the CLI can be done either through the CLI arguments or through a config file.

See the [Configuration](#configuration) section for more details and the guides.

### CLI Options

| Options                      |                                                                                                                                                                              |
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-v, --version`              | Display version number.                                                                                                                                                      |
| `--site <url>`               | Host URL to scan.                                                                                                                                                            |
| `--root <path>`              | Define the project root. Useful for changing where the config is read from or setting up sampling.                                                                           |
| `--config-file <path>`       | Path to config file.                                                                                                                                                         |
| `--output-path <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 <user-agent>`  | Specify a top-level user agent all requests will use.                                                                                                                        |
| `--router-prefix <path>`     | 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 <urls>`              | Specify explicit relative paths to scan as a comma-separated list, disabling the link crawler.<br>e.g. `unlighthouse --site unlighthouse.dev --urls /guide,/api,/config`    |
| `--exclude-urls <urls>`      | Route patterns to exclude as a comma-separated list. `*` matches one path segment, `**` matches any depth. They are not regex. <br>e.g. `unlighthouse --site unlighthouse.dev --exclude-urls /guide/**,/api/**` |
| `--include-urls <urls>`      | Route patterns to include as a comma-separated list. `*` matches one path segment, `**` matches any depth. They are not regex. <br>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 <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.

```ts
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
description: "Run unlighthouse-ci on every deploy. Auto-discovers every URL, runs Lighthouse on all of them in parallel, enforces a single budget. Works in GitHub Actions, GitLab CI, any pipeline."
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:

```bash
npm install @unlighthouse/cli
unlighthouse-ci --site https://staging.example.com --budget 80
```

Exit 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

```bash
# 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-static
```

## Already Using LHCI Server?

Keep your existing infrastructure—Unlighthouse uploads to LHCI servers:

```bash
unlighthouse-ci --site example.com \
  --reporter lighthouseServer \
  --lhci-host https://lhci.yourcompany.com \
  --lhci-build-token $LHCI_TOKEN
```

## Installation for CI

CI environments need Chrome. You do not need to install `puppeteer`:

```bash
npm install @unlighthouse/cli
```

Unlighthouse uses the system Chrome. If it finds none, it downloads a compatible Chrome to `~/.unlighthouse`. See [Chrome Dependency](/guide/guides/chrome-dependency).

## Performance Budgets

### Simple: One Number

```bash
# Fail if ANY category on ANY page < 75
unlighthouse-ci --site example.com --budget 75
```

### Per-Category Budgets

More control in your config:

```ts
// 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:
```bash
unlighthouse-ci
```

## Report 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` |

```bash
# Generate CSV for stakeholders
unlighthouse-ci --site example.com --reporter csvExpanded
```

## Static Reports

Generate a shareable HTML dashboard:

```bash
unlighthouse-ci --site example.com --build-static
```

Output goes to `.unlighthouse/`. Upload that folder to any static host.

**Live examples:**
- [inspect.unlighthouse.dev](https://inspect.unlighthouse.dev/)
- [vue-demo.unlighthouse.dev](https://vue-demo.unlighthouse.dev/)

## 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 <url>`          | Host URL to scan.                                                             |
| `--root <path>`         | Define the project root. Useful for changing where the config is read from or setting up sampling. |
| `--config-file <path>`  | Path to config file.                                                          |
| `--output-path <path>`  | Path to save the contents of the client and reports to.                       |
| `--budget <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 <user-agent>` | Specify a top-level user agent all requests will use.                     |
| `--router-prefix <path>` | 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 <urls>`         | Specify explicit relative paths to scan as a comma-separated list, disabling the link crawler. |
| `--exclude-urls <urls>` | Route patterns to exclude as a comma-separated list, such as `/admin/**`. Not regex. |
| `--include-urls <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 <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.

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  site: 'example.com',
  debug: true,
  scanner: {
    device: 'desktop',
  },
})
```

See the [Configuration](#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.

```yml unlighthouse.yml
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

```yaml
# .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 week
```

## CircleCI Example

```yaml
# .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: .unlighthouse
```

---

### 2.Integrations 3.Nuxt (`docs/2.integrations/3.nuxt.md`)

---
title: "Nuxt Integration"
icon: i-logos-nuxt-icon
description: "Integrate Lighthouse audits directly into your Nuxt development workflow with automatic route discovery."
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](/integrations/cli) or [CI](/integrations/ci) instead. See [deprecation notice](/integration-deprecations).
::

Lighthouse auditing during Nuxt development with automatic route discovery and HMR support.

## Install

::code-group

```bash [yarn]
yarn add -D @unlighthouse/nuxt
```

```bash [npm]
npm install -D @unlighthouse/nuxt
```

```bash [pnpm]
pnpm add -D @unlighthouse/nuxt
```

::

### Git ignore reports

Unlighthouse will save your reports in `outputDir` (`.unlighthouse` by default),
it's recommended you .gitignore these files.

```shell
.unlighthouse
```

## Usage

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

```js nuxt.config.ts
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  modules: [
    '@unlighthouse/nuxt',
  ],
})
```

### Nuxt 2

```js nuxt.config.js
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.

```json tsconfig.json
{
  "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

```js nuxt.config.js
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
description: "Integrate Lighthouse audits into your Vite development server with automatic route discovery and HMR support."
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](/integrations/cli) or [CI](/integrations/ci) instead. See [deprecation notice](/integration-deprecations).
::

Lighthouse auditing within your Vite dev environment with real-time performance feedback.

## Install

::code-group

```bash [yarn]
yarn add -D @unlighthouse/vite
```

```bash [npm]
npm install -D @unlighthouse/vite
```

```bash [pnpm]
pnpm add -D @unlighthouse/vite
```

::

### Git ignore reports

Unlighthouse will save your reports in `outputDir` (`.unlighthouse` by default),
it's recommended you .gitignore these files.

```shell
.unlighthouse
```

## Usage

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.

```ts vite.config.ts
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](https://github.com/hannoeru/vite-plugin-pages) plugin, you can provide route definitions to Unlighthouse.

You will need to hook into the plugin using the following code.

```ts vite.config.ts
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](/guide/guides/config) file
in the root directory.

### Example

```ts vite.config.ts
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
description: "Add Lighthouse auditing to webpack-based projects with development server integration and HMR support."
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](/integrations/cli) or [CI](/integrations/ci) instead. See [deprecation notice](/integration-deprecations).
::

Lighthouse auditing for webpack-based applications during development.

## Install

::code-group

```bash [yarn]
yarn add -D @unlighthouse/webpack
```

```bash [npm]
npm install -D @unlighthouse/webpack
```

```bash [pnpm]
pnpm add -D @unlighthouse/webpack
```

::

### Git ignore reports

Unlighthouse will save your reports in `outputDir` (`.unlighthouse` by default),
it's recommended you .gitignore these files.

```shell
.unlighthouse
```

## Usage

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

```js webpack.config.js
import Unlighthouse from '@unlighthouse/webpack'

export default {
  // ...
  plugins: [
    Unlighthouse({
      // config
    }),
  ],
}
```

### CJS example

```js webpack.config.js
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](/guide/guides/config)
in the root directory.

### Example

```js webpack.config.ts
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"
description: "Complete reference for all Unlighthouse configuration options including types, defaults, and usage examples."
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](/guide/guides/config) or [CLI Arguments](/integrations/cli#configuration).

::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.ts`
- `production-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<UnlighthouseHooks>`{lang="ts"}
- **Default:** `{}`{lang="ts"}

Hooks to run. See the [Hooks](/api-doc#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` .

```ts
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.

See [Manually Providing URLs](/guide/guides/url-discovery#manually-providing-urls).

## CI Options

Change the behaviour of unlighthouse in CI mode.

### `ci.budget`{lang="ts"}

- **Type:** `number|Record<Partial<LighthouseCategories>, 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:

```ts
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](/guide/recipes/client) for more information.

### `client.columns`{lang="ts"}

- **Type:** `Record<UnlighthouseTabs, UnlighthouseColumn[]>`{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](/guide/guides/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](/guide/guides/route-definitions#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](/guide/recipes/large-sites#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](/guide/recipes/large-sites#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](/guide/recipes/spa) 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](https://github.com/GoogleChrome/lighthouse/blob/master/docs/variability.md#run-lighthouse-multiple-times)
and [Improving Accuracy](/guide/recipes/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](/guide/guides/device#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](/guide/guides/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](/guide/recipes/large-sites#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](/guide/guides/device) for more information.

## Lighthouse Options

Changes the default behaviour of Google Lighthouse.

See [Configure Google Lighthouse](/guide/guides/lighthouse) for more information.

## Puppeteer Options

Change the behaviour of puppeteer.

See [puppeteer.connect(options)](https://pptr.dev/#?product=Puppeteer&version=v13.0.1&show=api-puppeteerconnectoptions)
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)](https://github.com/thomasdondorf/puppeteer-cluster#clusterlaunchoptions) for available
configuration.

---

### 3.Api Doc Glossary (`docs/3.api-doc/glossary.md`)

---
title: "API Glossary"
description: "Key terms and concepts used throughout the Unlighthouse documentation and API, including route definitions, providers, and hooks."
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](/guide/guides/dynamic-sampling) and better organization.

The page component has multiple representations:

1. _static route_ - name matches the path (/about.vue -> /about/),
2. _dynamic route_ - a query is used to generate a set of paths (/posts/:id.vue -> /posts/my-first-post/)
3. _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

```ts
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.

```ts
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.

```ts
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).

```ts
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:

- `inspectHtmlTask` which dumps the HTML of the URL and extracts SEO data from it (title, description, image, internal links, etc)
- `runLighthouseTask` runs the actual lighthouse process on the URL

See [cluster.task(fn)](https://github.com/thomasdondorf/puppeteer-cluster) for more details.

```ts
/**
 * 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).

```ts
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)?"
description: "Learn what Cumulative Layout Shift means, why it matters for Core Web Vitals, and how to prevent layout shifts on your website."
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](/glossary) 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](https://web.dev/articles/cls) 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](/guide/getting-started/unlighthouse-cli) 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](https://pagespeed.web.dev/)
- Web Vitals Chrome extension

### Measure in Browser

Run this in your browser console to track CLS in real-time. Based on [webperf-snippets](https://webperf-snippets.nucliweb.net/CoreWebVitals/CLS).

```ts
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:

1. **Always set image dimensions** - Use `width` and `height` attributes or CSS aspect-ratio
2. **Reserve space for ads** - Use fixed containers for ad slots
3. **Preload fonts** - Use `font-display: optional` or preload critical fonts
4. **Avoid inserting content above existing content** - Add new elements below the fold or use transforms
5. **Use CSS transforms for animations** - Transform doesn't trigger layout recalculation

::tip
Use `aspect-ratio` CSS property for responsive images:

```css
img {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
}
```

::

---

### Glossary Fcp (`docs/glossary/fcp.md`)

---
title: "What is First Contentful Paint (FCP)?"
description: "Learn what First Contentful Paint measures, its thresholds, and how to improve FCP for better perceived load speed."
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](https://web.dev/articles/fcp) an FCP of 1.8 seconds or less for at least 75% of page visits.

## FCP vs LCP

FCP and [LCP](/glossary/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](/glossary/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](/guide/getting-started/unlighthouse-cli) to measure FCP across your entire site.

For individual pages:

- Chrome DevTools Performance panel
- Lighthouse in Chrome
- [PageSpeed Insights](https://pagespeed.web.dev/)
- Web Vitals Chrome extension

### Measure in Browser

Run this in your browser console to see FCP timing.

```ts
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:

1. **Reduce server response time** - Optimize [TTFB](/glossary/ttfb) with caching and CDNs
2. **Eliminate render-blocking resources** - Defer non-critical CSS/JS, inline critical CSS
3. **Minify CSS** - Remove unused styles and compress what remains
4. **Preconnect to origins** - Use `<link rel="preconnect">` for third-party domains
5. **Avoid redirects** - Each redirect adds latency
6. **Optimize font loading** - Use `font-display: swap` or `optional`

::note
FCP is a Lighthouse lab metric, not a Core Web Vital. However, improving FCP typically improves [LCP](/glossary/lcp), which is a Core Web Vital and ranking factor.
::

---

### Glossary Inp (`docs/glossary/inp.md`)

---
title: "What is Interaction to Next Paint (INP)?"
description: "Learn what Interaction to Next Paint means, why it replaced FID as a Core Web Vital, and how to improve your site's responsiveness."
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)](https://web.dev/blog/inp-cwv-march-12) as a [Core Web Vital](/glossary) 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](https://web.dev/articles/inp) 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](/guide/getting-started/unlighthouse-cli) 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](https://pagespeed.web.dev/) - 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.

```ts
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:

1. **Break up long tasks** - Use `setTimeout` or `scheduler.yield()` to yield to the main thread
2. **Defer non-critical work** - Move analytics and non-essential code out of interaction handlers
3. **Use web workers** - Offload heavy computation off the main thread
4. **Optimize event handlers** - Do the minimum work needed, then update UI
5. **Reduce DOM size** - Fewer elements mean faster rendering
6. **Debounce rapid interactions** - Avoid processing every keystroke

```js
// 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)?"
description: "Learn what Largest Contentful Paint means, why it matters for Core Web Vitals, and how to measure and improve LCP scores."
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](/glossary) 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](https://web.dev/articles/lcp) 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](/guide/getting-started/unlighthouse-cli) 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](https://pagespeed.web.dev/)
- 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](https://webperf-snippets.nucliweb.net/CoreWebVitals/LCP).

```ts
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:

1. **Optimize your server** - Use caching, CDNs, and efficient backend code
2. **Preload critical resources** - Use `<link rel="preload">` for LCP images
3. **Compress images** - Use modern formats (WebP, AVIF) and proper sizing
4. **Remove render-blocking resources** - Defer non-critical CSS/JS
5. **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)?"
description: "Learn what Total Blocking Time measures, its thresholds, and how to reduce main thread blocking for better interactivity."
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](/glossary/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:

```text
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](/glossary/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](/guide/getting-started/unlighthouse-cli) to measure TBT across your entire site.

For individual pages:

- Lighthouse in Chrome DevTools
- [PageSpeed Insights](https://pagespeed.web.dev/) - Lab data section
- WebPageTest

### Measure in Browser

Run this in your browser console to track Long Tasks that contribute to TBT.

```ts
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:

1. **Break up Long Tasks** - Split large functions using `setTimeout`, `requestIdleCallback`, or `scheduler.yield()`
2. **Reduce JavaScript** - Remove unused code, tree-shake dependencies
3. **Code split** - Load only what's needed for initial view
4. **Defer third-party scripts** - Load analytics and ads after critical content
5. **Use web workers** - Move heavy computation off the main thread
6. **Optimize CSS** - Large stylesheets can block the main thread during parsing

```ts
// 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](/glossary/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)?"
description: "Learn what Time to First Byte measures, its thresholds, and how to optimize server response time for faster page loads."
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](/glossary/fcp), [LCP](/glossary/lcp), and full page load.

## TTFB Score Thresholds

| Score | Rating |
|-------|--------|
| ≤ 800ms | Good |
| 800ms - 1800ms | Needs Improvement |
| > 1800ms | Poor |

[Google recommends](https://web.dev/articles/ttfb) 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](/guide/getting-started/unlighthouse-cli) to audit TTFB across your entire site.

For individual pages:

- Chrome DevTools Network panel (look for "Waiting for server response")
- [PageSpeed Insights](https://pagespeed.web.dev/) - 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](https://webperf-snippets.nucliweb.net/Loading/TTFB).

```ts
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:

1. **Use a CDN** - Serve content from edge locations closer to users
2. **Enable caching** - Cache responses at CDN, server, and application levels
3. **Optimize server code** - Profile and fix slow database queries, inefficient code
4. **Upgrade hosting** - More CPU, memory, or faster infrastructure
5. **Use HTTP/2 or HTTP/3** - Modern protocols reduce connection overhead
6. **Minimize redirects** - Each redirect adds a full round-trip
7. **Optimize DNS** - Use a fast, reliable DNS provider
8. **Preconnect to origins** - `<link rel="preconnect">` for known third-party domains

::note
TTFB is not a Core Web Vital, but it directly impacts [LCP](/glossary/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)?"
description: "Learn what Time to Interactive measured, why it was deprecated in Lighthouse 10, and what metrics replaced it."
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](/glossary/tbt) for lab measurement and [INP](/glossary/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](/glossary/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](https://developer.chrome.com/blog/lighthouse-10-0) 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](/glossary/lcp) and Speed Index better indicate when content feels loaded. [TBT](/glossary/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)](/glossary/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](/glossary/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)](/glossary/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](/glossary/tbt) (30% of score) |
| TTI in field/RUM | Use [INP](/glossary/inp) (Core Web Vital) |
| TTI CI assertions | Switch to TBT thresholds |

For most sites, the removal of TTI [improved Lighthouse scores](https://www.searchenginejournal.com/lighthouse-10-tti-removal/479789/) since pages typically score better on CLS than they did on TTI.

## Measuring Interactivity Today

Use [Unlighthouse](/guide/getting-started/unlighthouse-cli) to measure TBT across your site for lab data.

For real-user interactivity:

- Chrome User Experience Report (CrUX) for INP field data
- [PageSpeed Insights](https://pagespeed.web.dev/) 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"
description: "Install and run Unlighthouse CLI to scan your entire website with Lighthouse. npm, pnpm, and yarn installation options."
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.

```bash
npx unlighthouse --site example.com
```

> [!TIP]
> Using an AI agent? Get the unlighthouse Skill on [skilld.dev/gh/harlan-zw/unlighthouse](https://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](/glossary) for what [LCP](/glossary/lcp), [CLS](/glossary/cls), and [INP](/glossary/inp) actually mean.
::

## Quick Start

Pick your package manager:

::code-group

```bash [npm]
npx unlighthouse --site https://mysite.com
```

```bash [pnpm]
pnpm dlx unlighthouse --site https://mysite.com
```

```bash [yarn]
yarn 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

1. **Discovery** — Unlighthouse reads your sitemap and crawls internal links to find every page
2. **Parallel scanning** — Multiple Chrome instances run Lighthouse audits simultaneously
3. **Live results** — Scores stream to your dashboard as each page completes
4. **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:

```bash
npm install unlighthouse
unlighthouse --site example.com
```

## Common First Scan Issues

### Nothing happening?

Add `--debug` to see what's going on:

```bash
npx unlighthouse --site example.com --debug
```

### WSL Users

Chrome can be tricky in WSL. If you see connection errors, check the [WSL troubleshooting guide](/guide/guides/common-errors#connect-econnrefused-127001port).

### 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:

```ts
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](/guide/guides/config).

## What's Next?

- **[CI Integration](/integrations/ci)** — Run scans on every deploy, fail builds on performance regressions
- **[Large Sites](/guide/recipes/large-sites)** — Scanning thousands of pages efficiently
- **[Authentication](/guide/guides/authentication)** — Scan pages behind login

## Get Help

- **[Discord](https://discord.gg/275MBUBvgP)** — Quick questions and community help
- **[GitHub Issues](https://github.com/harlan-zw/unlighthouse/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"
description: "Install and run Unlighthouse CLI to scan your entire website with Lighthouse. npm, pnpm, and yarn installation options."
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.

```bash
npx unlighthouse --site example.com
```

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](/glossary) for what [LCP](/glossary/lcp), [CLS](/glossary/cls), and [INP](/glossary/inp) actually mean.
::

## Quick Start

Pick your package manager:

::code-group

```bash [npm]
npx unlighthouse --site https://mysite.com
```

```bash [pnpm]
pnpm dlx unlighthouse --site https://mysite.com
```

```bash [yarn]
yarn 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

1. **Discovery** — Unlighthouse reads your sitemap and crawls internal links to find every page
2. **Parallel scanning** — Multiple Chrome instances run Lighthouse audits simultaneously
3. **Live results** — Scores stream to your dashboard as each page completes
4. **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:

```bash
npm install unlighthouse
unlighthouse --site example.com
```

## Common First Scan Issues

### Nothing happening?

Add `--debug` to see what's going on:

```bash
npx unlighthouse --site example.com --debug
```

### WSL Users

Chrome can be tricky in WSL. If you see connection errors, check the [WSL troubleshooting guide](/guide/guides/common-errors#connect-econnrefused-127001port).

### 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:

```ts
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](/guide/guides/config).

## What's Next?

- **[CI Integration](/integrations/ci)** — Run scans on every deploy, fail builds on performance regressions
- **[Large Sites](/guide/recipes/large-sites)** — Scanning thousands of pages efficiently
- **[Authentication](/guide/guides/authentication)** — Scan pages behind login

## Get Help

- **[Discord](https://discord.gg/275MBUBvgP)** — Quick questions and community help
- **[GitHub Issues](https://github.com/harlan-zw/unlighthouse/issues)** — Bug reports and feature requests

---

### 1.Guide 1.Getting Started 1.Integrations (`docs/1.guide/1.getting-started/1.integrations.md`)

---
title: "Integrations"
description: "Integrate Unlighthouse into your existing build tools, frameworks, and CI/CD pipelines for automated Lighthouse testing."
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](/integrations/cli) | Scan a production site such as [unlighthouse.dev](/).<br><br> You can manually provide a project mapping for [routes definitions](/guide/guides/route-definitions).                                                          |
| [CI](/integrations/ci)   | Run scans on sites based on automation events, i.e releasing and make [assertions on scores](/integrations/ci#assertions).<br><br> Can also be used to generate report sites such as [inspect.unlighthouse.dev](https://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](/integration-deprecations)
::

| Provider | Features |
|----------|----------|
| [Nuxt.js](/integrations/nuxt) | Hot Module Reloading, Automatic Route Discovery |
| [Vite](/integrations/vite) | Hot Module Reloading, Automatic Route Discovery |
| [webpack](/integrations/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"
description: "Learn how Unlighthouse automatically discovers pages, runs Lighthouse audits in parallel, and generates site-wide performance reports."
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](https://github.com/unjs/c12) package. This supports TypeScript config files and gives you a global `defineUnlighthouseConfig` function for type support:

::code-group

```ts [Configuration Loading]
const unlighthouse = await createUnlighthouse(config)
```

```ts [unlighthouse.config.ts]
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](https://github.com/thomasdondorf/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:

```ts
// Create server for the UI client
const { server, app } = await createServer()
await unlighthouse.setServerContext({
  url: server.url,
  server: server.server,
  app
})
```

#### API

The [unrouted](https://github.com/harlan-zw/unrouted) API handles:

- Sending scan updates to your browser
- Serving Lighthouse reports
- Managing the scanning process

#### Web App

A [Vite](https://github.com/vitejs/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

```ts [Immediate Start]
// CLI mode - starts immediately
unlighthouse.start()
```

```ts [Lazy 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:

1. **Route Files**: Reads your framework's route files (like Next.js pages)
2. **Robots.txt**: Checks your robots.txt file for sitemap links
3. **Sitemap.xml**: Gets all URLs from your sitemap
4. **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](/glossary) like [LCP](/glossary/lcp), [CLS](/glossary/cls), [INP](/glossary/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"
description: "Configure Unlighthouse for your specific needs using configuration files and inline options."
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:

```ts
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.ts`
- `unlighthouse.config.js`
- `unlighthouse.config.mjs`

Or specify one explicitly:
```bash
unlighthouse --config-file ./configs/production.config.ts
```

## Common Configs

### Skip Paths You Don't Care About

```ts
export default defineUnlighthouseConfig({
  site: 'https://example.com',
  scanner: {
    exclude: [
      '/api/**', // API routes
      '/admin/**', // Admin pages
      '/amp/**', // AMP versions
    ],
  },
})
```

Or scan *only* specific paths:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  scanner: {
    device: 'desktop',
  },
})
```

### More Accurate Scores

Lighthouse scores vary between runs. For reliable numbers:

```ts
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)

```ts
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](/guide/guides/authentication).

### CI Performance Budgets

Fail your build if scores drop:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  lighthouseOptions: {
    onlyCategories: ['performance', 'accessibility'], // Skip SEO/best-practices
    skipAudits: ['uses-http2'], // Ignore specific audits
    throttlingMethod: 'devtools',
  },
})
```

### React to Scan Events

```ts
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

```ts
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](/api-doc/config) for every option with types and defaults.

---

### 1.Guide Guides 1.Debugging (`docs/1.guide/guides/1.debugging.md`)

---
title: "Debugging Lighthouse Scans"
description: "Debug and troubleshoot Unlighthouse scans using logging, browser inspection, and diagnostic tools."
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:

```bash
unlighthouse --site example.com --debug
```

This 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:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  lighthouseOptions: {
    maxWaitForLoad: 60000, // Wait up to 60s (default: 45s)
  },
})
```

### SPA Not Rendering

JavaScript apps need time to hydrate:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    ignoreHTTPSErrors: true,
    args: ['--ignore-certificate-errors'],
  },
})
```

### Chrome Won't Start

Try using bundled Chromium instead of system Chrome:

```ts
export default defineUnlighthouseConfig({
  chrome: {
    useSystem: false, // Download and use bundled Chromium
  },
})
```

Or point to a specific Chrome:
```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    executablePath: '/usr/bin/google-chrome-stable',
  },
})
```

### Failed Network Requests

Log all failed requests to find what's breaking:

```ts
export default defineUnlighthouseConfig({
  hooks: {
    'puppeteer:before-goto': async (page) => {
      page.on('requestfailed', (req) => {
        console.log('❌ Failed:', req.url(), req.failure()?.errorText)
      })
    },
  },
})
```

## Still Stuck?

1. Check [Common Errors](/guide/guides/common-errors) for known issues
2. Search [GitHub Issues](https://github.com/harlan-zw/unlighthouse/issues)
3. Ask on [Discord](https://discord.gg/275MBUBvgP) with your debug logs

---

### 1.Guide Guides Authentication (`docs/1.guide/guides/authentication.md`)

---
title: "Authentication for Lighthouse Scans"
description: "Scan password-protected websites with Unlighthouse. Configure basic auth, cookies, headers, localStorage, and programmatic login flows."
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):

```ts
export default defineUnlighthouseConfig({
  auth: {
    username: process.env.AUTH_USER,
    password: process.env.AUTH_PASS,
  },
})
```

Or via CLI:
```bash
unlighthouse --site staging.example.com --auth admin:secretpass
```

## Cookies

Most common for session-based auth. Grab your session cookie from browser DevTools (Application → Cookies):

```ts
export default defineUnlighthouseConfig({
  cookies: [
    {
      name: 'session_id',
      value: 'abc123...',
      domain: 'example.com', // Must match your site
      path: '/',
    },
  ],
})
```

**Getting the cookie value:**
1. Log into your site in Chrome
2. Open DevTools → Application → Cookies
3. Copy the session cookie value
4. Paste into config (or use environment variable)

CLI shorthand:
```bash
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:

```ts
export default defineUnlighthouseConfig({
  extraHeaders: {
    Authorization: `Bearer ${process.env.API_TOKEN}`,
  },
})
```

CLI:
```bash
unlighthouse --site api.example.com --extra-headers "Authorization:Bearer abc123"
```

## Query Params

Some staging environments use URL tokens:

```ts
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:

```ts
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:

```ts
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"]', 'test@example.com')
      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:

```ts
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:

```ts
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](/guide/guides/debugging) for more techniques.

---

### 1.Guide Guides Chrome Dependency (`docs/1.guide/guides/chrome-dependency.md`)

---
title: "Chrome Dependency"
description: "Configure Chrome browser settings for Unlighthouse scanning, including system Chrome usage and custom installations."
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.

```ts
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 installer
- `chrome.downloadFallbackVersion` - Which Chrome for Testing version to download (default: the version pinned by the installed `puppeteer-core`)
- `chrome.downloadFallbackCacheDir` - Where the binary should be saved (default `$home/.unlighthouse`)

```ts
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`.

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    executablePath: '/usr/bin/chrome',
  },
})
```

---

### 1.Guide Guides Common Errors (`docs/1.guide/guides/common-errors.md`)

---
title: "Common Errors"
description: "Troubleshoot common issues encountered when running Unlighthouse scans, including browser connection and environment problems."
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](/guide/guides/debugging).
::

## `connect ECONNREFUSED 127.0.0.1:<port>`

**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](https://pptr.dev/troubleshooting#running-puppeteer-on-wsl-windows-subsystem-for-linux).
- Install Chrome in WSL following the [documentation](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps#install-google-chrome-for-linux).

**Other Environments**

- You can try disabling the system Chrome, instead using the fallback.

```ts
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](https://github.com/rprichard/winpty), which ships with Git for Windows:

```bash
winpty npx unlighthouse --site example.com
```

- Or run the command from **PowerShell**, **Command Prompt**, or **Windows Terminal** instead of Git Bash, where `Ctrl+C` works as expected.

---

### 1.Guide Guides Device (`docs/1.guide/guides/device.md`)

---
title: "Unlighthouse --desktop Flag & Device Configuration"
description: "Run Unlighthouse in desktop mode with the --desktop flag: npx unlighthouse --site <url> --desktop. Configure mobile, desktop, or custom viewports with throttling."
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:

```bash
npx unlighthouse --site https://example.com --desktop
```

This 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

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  scanner: {
    device: 'desktop',
  },
})
```

### Mobile Scanning (Default)

```ts
export default defineUnlighthouseConfig({
  scanner: {
    device: 'mobile',
  },
})
```

## Custom Dimensions

Test specific viewport sizes for responsive breakpoints:

```ts
export default defineUnlighthouseConfig({
  lighthouseOptions: {
    screenEmulation: {
      width: 1800,
      height: 1000,
    },
  },
})
```

## Network Throttling

Throttling simulates slower network and CPU conditions for more realistic performance testing:

```ts
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"
description: "Run Unlighthouse site-wide Lighthouse scans in Docker containers. Dockerfile examples and CI/CD configuration."
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.

```ts
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.

```ts
export default defineUnlighthouseConfig({
  server: {
    open: false,
  },
  hooks: {
    'worker-finished': async () => {
      process.exit(0)
    },
  },
})
```

## Docker File

Please see the following community repos:

- [indykoning—Unlighthouse Docker](https://github.com/indykoning/unlighthouse-docker)

---

### 1.Guide Guides Dynamic Sampling (`docs/1.guide/guides/dynamic-sampling.md`)

---
title: "Dynamic Sampling"
description: "Automatically sample similar pages to reduce scan time for sites with many similar URLs like blogs or e-commerce."
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.

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  scanner: {
    dynamicSampling: 10, // Number of samples per group (default: 8)
  },
})
```

### Disable Dynamic Sampling

```ts
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"
description: "Generate static Lighthouse reports for your entire site. Export as HTML, CSV, or JSON. Deploy to Netlify, CloudFlare, or any static host."
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

```bash [npm]
npm install @unlighthouse/cli
```

```bash [yarn]
yarn global add @unlighthouse/cli
```

```bash [pnpm]
pnpm install @unlighthouse/cli
```

::

For complete CI features, see the [CI Integration Guide](/integrations/ci).

## 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.

```bash
unlighthouse-ci --site <your-site> --build-static
```

This 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](https://developers.cloudflare.com/pages/platform/direct-upload/).

You will use the [wrangler](https://developers.cloudflare.com/pages/platform/using-wrangler) CLI to upload the static report.

You will need to init wrangler and configure it for your requirements.

```bash
wrangler init
```

You can then run the following command to generate the static report and upload it to CloudFlare Pages.

```bash
unlighthouse-ci --site www.example.com --build-static && wrangler pages publish .unlighthouse
```

### GitHub Actions & Netlify Example

This example is for GitHub Actions and deploys a static client build to Netlify.

```yml unlighthouse.yml
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/actions-netlify@v3.0
        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: 1
```

## CSV Reports

You can generate a CSV report by providing the `--reporter csv` or `--reporter csvExpanded` flag.

```bash
unlighthouse-ci --site <your-site> --reporter csv
```

Note: This report format is experimental and may change in the future.

This will generate a report like the following (`csvExpanded` sample):

```csv
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,1
```

## JSON Reports

You can generate a JSON report by providing the `--reporter json` or `--reporter jsonExpanded` flag.

```bash
unlighthouse-ci --site <your-site> --reporter json
```

Note: 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.

```bash
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.

```bash
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"
description: "Customize Google Lighthouse audit settings, categories, and performance thresholds within Unlighthouse scans."
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.

```ts
import { defineUnlighthouseConfig } from 'unlighthouse/config'

export default defineUnlighthouseConfig({
  lighthouseOptions: {
    throttlingMethod: 'devtools',
  },
})
```

For complete options, see the [Lighthouse Configuration docs](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md).

## Aliases

Unlighthouse aims to minimise and simplify configuration, where possible.

For this reason, a number of configurations aliases are provided for your convenience.

- [Switching device: mobile and desktop](/guide/guides/device)
- [Toggle Throttling](/guide/guides/device#network-throttling)

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](/glossary) including [LCP](/glossary/lcp), [CLS](/glossary/cls), and [INP](/glossary/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**

```ts
export default defineUnlighthouseConfig({
  lighthouseOptions: {
    onlyCategories: ['performance'],
  },
})
```

---

### 1.Guide Guides Puppeteer (`docs/1.guide/guides/puppeteer.md`)

---
title: "Puppeteer Launch Options"
description: "Configure Puppeteer launch options in Unlighthouse: headless mode, Chrome args, viewport settings, executable path, and navigation hooks."
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](https://pptr.dev/) to control Chrome for Lighthouse audits. Configure browser behavior, navigation hooks, and Chrome flags via `puppeteerOptions`.

## All Available Options

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    // See: https://pptr.dev/api/puppeteer.launchoptions
  },
})
```

Full reference: [Puppeteer LaunchOptions API](https://pptr.dev/api/puppeteer.launchoptions)

## Common Configurations

### Headless Mode

```ts
// Run with visible browser (debugging)
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    headless: false,
  },
})
```

### Custom Chrome Executable

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    executablePath: '/usr/bin/google-chrome',
  },
})
```

### Chrome Arguments

Common args for CI/Docker environments:

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
      '--disable-gpu',
    ],
  },
})
```

### Viewport Settings

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
  },
})
```

### Timeout Configuration

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    timeout: 60000, // 60 seconds
  },
})
```

### User Data Directory

Persist browser data between runs:

```ts
export default defineUnlighthouseConfig({
  puppeteerOptions: {
    userDataDir: './.puppeteer-data',
  },
})
```

## Navigation Hooks

Hook into Puppeteer's page navigation for custom logic.

### Before Page Load

```ts
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

```ts
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

See [Chrome Dependency Guide](/guide/guides/chrome-dependency).

### 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"
description: "Configure route discovery and custom sampling patterns for better page organization and intelligent scanning."
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](/guide/guides/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.

```ts
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.

```ts
export default defineUnlighthouseConfig({
  scanner: {
    customSampling: {
      '/q-(.*?)': {
        name: 'search-query',
      },
    },
  },
})
```

---

### 1.Guide Guides Url Discovery (`docs/1.guide/guides/url-discovery.md`)

---
title: "URL Discovery"
description: "How Unlighthouse discovers pages using sitemaps, robots.txt, and internal link crawling. Configure URL sources and filters."
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:

1. Add the specified `site` from `--site` or config
2. Manually providing URLs via the `--urls` flag or `urls` on the provider.
3. `robotsTxt` - Reading robots.txt, if it exists. Provides sitemap URLs and disallowed paths.
4. `sitemap` - Reading sitemap.xml, if it exists
5. `crawler` - Inspecting internal links
6. Using provided static [route definitions](/api-doc/glossary#route-definition)

## 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.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
export default defineUnlighthouseConfig({
  urls: [
    '/about',
    '/other-page',
  ],
})
```

Or you can return a function or promise.

```ts
export default defineUnlighthouseConfig({
  urls: async () => await getUrls(),
})
```

Specify explicit relative URLs as a comma-separated list.

```bash
unlighthouse --site https://example.com --urls /about,/other-page
```

---

### 1.Guide Recipes Client (`docs/1.guide/recipes/client.md`)

---
title: "Customizing the UI"
description: "Modify Unlighthouse client interface columns and display to show custom metrics and data."
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

```ts
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](/api-doc/glossary#columns) for all available column options.
::

---

### 1.Guide Recipes Improving Accuracy (`docs/1.guide/recipes/improving-accuracy.md`)

---
title: "Improving Lighthouse Accuracy"
description: "Optimize Lighthouse scan accuracy with multiple samples and reduced concurrency for more reliable, consistent Core Web Vitals results."
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](/glossary) 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:

```ts
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:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  scanner: {
    throttle: true, // Simulate 4G network
  },
})
```

## Recommended Production Config

For the most accurate results:

```ts
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"
description: "Scan large websites with thousands of pages efficiently. Configure sampling, URL filtering, and optimization strategies for bulk Lighthouse testing."
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](/api-doc/config#scanner-ignorei18npages) enabled
- [maxRoutes](/api-doc/config#scanner-maxroutes) set to 200
- [skipJavascript](/api-doc/config#scanner-skipjavascript) enabled
- [samples](/api-doc/config#scanner-samples) set to 1
- [throttling](/api-doc/config#scanner-throttle) enabled, except for local sites
- [crawler](/api-doc/config#scanner-crawler) enabled
- [dynamicSampling](/api-doc/config#scanner-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](/guide/guides/url-discovery#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/guides/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 to
`blog-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.

```ts
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.

```ts
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:

```ts
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:

```ts
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"
description: "Configure Unlighthouse to scan single-page applications (SPAs) with client-side routing like React, Vue, and Angular apps."
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](/glossary) measurement.

## Enable JavaScript Execution

Allow Puppeteer to execute JavaScript before extracting page content:

```ts
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:

```ts
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:

```ts
export default defineUnlighthouseConfig({
  urls: [
    '/',
    '/about',
    '/products',
    '/contact',
  ],
  scanner: {
    skipJavascript: false,
  },
})
```

## SPA Performance Considerations

SPAs typically have worse [LCP](/glossary/lcp) scores because:
- Content renders after JavaScript execution
- Initial HTML is often empty or minimal
- Hydration adds to [INP](/glossary/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
