Lighthouse's mobile Total Blocking Time curve: 200 ms scores 0.90 and 600 ms scores 0.50, and a shaded band marks 300 ms at 0.79 and 400 ms at 0.68. At a 30% weight that gap is about 3 points, so compare the median of 5 runs.

Lighthouse performance score explained: how the scoring model works

Five lab numbers go in and one score comes out. Once you can redo that sum by hand, you know which metric holds your missing points and why the number moves when nothing changed.

How is the Lighthouse performance score calculated?#

Lighthouse maps five lab metrics onto 0 to 1 scores and takes their weighted mean, with Total Blocking Time at 30 percent in the GoogleChrome Lighthouse source as of October 2026. The Chrome scoring docs list the same Lighthouse 10 weights. Also, they say the metric scores "are not visible in the report". So the report shows raw values, and the scores sit under the hood.

Show data table
Lighthouse 10 weights as a share of the Performance score, with FCP and Speed Index shown together; Source: GoogleChrome/lighthouse default-config.js, main branch read 2026-10-09
Segment Value (%) Share
Total Blocking Time 30 30%
Largest Contentful Paint 25 25%
Cumulative Layout Shift 25 25%
First Contentful Paint and Speed Index (10 each) 20 20%

TBT and LCP together carry more than half of the score.

Figure Lighthouse 10 weights as a share of the Performance score, with FCP and Speed Index shown together; Source: GoogleChrome/lighthouse default-config.js, main branch read 2026-10-09 Arithmetic from GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09 (FCP 10 plus Speed Index 10)

In short, TBT and LCP together carry more than half of the score, so they decide most of what you see.

Why does the weighting decide which fix ships first?#

In the worked example, cutting TBT from 600 to 300 ms lifts the score from 73 to 81, while a larger LCP cut reaches only 80. Because the weights and the curve slopes differ, a week of work buys a different gain on each metric. So picking the metric is the real decision, and the size of the fix comes second.

Show data table
Performance score after each fix in the worked example (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09
Item Value
the run as measured 73
TBT cut from 600 to 300 ms 81
LCP cut from 3630 to 2500 ms 80
both fixes 89
the same raw values on desktop curves 42

The TBT cut moves the score more than the bigger LCP cut.

Figure Performance score after each fix in the worked example (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09 GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09

In practice, TBT falls when less JavaScript runs on the main thread during load. In particular, your rendering choices on Cloudflare or elsewhere decide much of that. Meanwhile, a slow server response delays both FCP and LCP. Common MySQL bottlenecks are a frequent cause of that delay.

How does a raw metric value become a 0 to 1 score?#

Each metric has two control points in the GoogleChrome Lighthouse source as of October 2026, the value scoring 0.90 and the value scoring 0.50, such as 2500 and 4000 ms for mobile LCP. In the Chrome scoring docs, the 8th percentile of HTTP Archive data "becomes a score of 90 (the good/green control point)". Likewise, the 25th percentile becomes the median control point at 50.

Those two points fix a log-normal curve, and any raw value can then be read off it. Lighthouse's getLogNormalScore function in statistics.js does that step. Then computeLogNormalScore in util.js adds a small boost above 0.90 and rounds each score down to two decimals.

Mobile control points, in milliseconds (CLS unitless). Source: the five metric audit files in GoogleChrome/lighthouse, main branch read 2026-10-09

metricvalue that scores 0.90 (p10)value that scores 0.50 (median)
FCP18003000
Speed Index33875800
LCP25004000
TBT200600
CLS0.10.25

As a result, a metric at its 0.90 point is good, while a metric at its 0.50 point is only average.

What does one mobile run look like, worked through?#

In this worked example (assumption values), FCP 1800 ms, SI 3700 ms, LCP 3630 ms, TBT 600 ms and CLS 0.02 score 0.90, 0.85, 0.60, 0.50 and 1.00, for 73. The table below multiplies each score by its weight. Then the arithmeticMean function in Lighthouse's scoring.js divides by the total weight and rounds the result to two decimals.

Illustrative worked example (assumption values): points per metric, scored with the GoogleChrome Lighthouse code read 2026-10-09

metricraw value (assumption)metric scoreweightpoints
FCP1800 ms0.90109.0
SI3700 ms0.85108.5
LCP3630 ms0.602515.0
TBT600 ms0.503015.0
CLS0.021.002525.0

Illustrative: assumption raw values, scored with the GoogleChrome Lighthouse code read 2026-10-09.

Show data table
Illustrative: the worked example's weighted points as a running total (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09. Modelled, not measured
Step Change Running total
0 0 points 0 points
FCP 9 points 9 points
SI 8.5 points 17.5 points
LCP 15 points 32.5 points
TBT 15 points 47.5 points
CLS 25 points 72.5 points
Total points 72.5 points

The five weighted scores add up to 72.5 points, which the report prints as 73.

Figure Illustrative: the worked example's weighted points as a running total (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09. Modelled, not measured Arithmetic from GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09. Modelled, not measured

The points add to 72.5, so the mean is 0.725. That rounds to 0.73, and the report prints it as 73. Therefore, you can check any report the same way, because it already shows every raw value you need.

Show data table
Metric scores in the worked example (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09
Item Value
First Contentful Paint 0.9
Speed Index 0.85
Largest Contentful Paint 0.6
Total Blocking Time 0.5
Cumulative Layout Shift 1

TBT at 0.50 and LCP at 0.60 are the two scores pulling the total down.

Figure Metric scores in the worked example (assumption values); arithmetic from the GoogleChrome Lighthouse scoring code read 2026-10-09 GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09

Which metric should you fix first?#

In the worked example, Total Blocking Time holds 15 of the points still missing, more than LCP's 10 and the other three metrics combined. The rule is short: the points left on a metric are one minus its score, times its weight. For example, TBT at 0.50 with weight 30 leaves 15 points on the table.

Try it

Drag each metric sub-score. The composite recomputes live.

73/ 100
Needs improvementweighted composite 0.725

TBT has the most points still on the table. It carries 15 unearned points at a 30% weight, more than any other metric on the page. Raising a metric that already sits near 1.00 adds almost nothing, so spend the effort where the weight and the gap are both large.

Weighted contribution per metric (current Lighthouse bundle)
MetricWeightSub-scorePoints earnedPoints available
FCP10%0.991
SI10%0.858.51.5
LCP25%0.61510
TBT30%0.51515
CLS25%1250
Total100%7327

An illustrative teaching model, not a benchmark. The weights are the published current Lighthouse 10 to 13 bundle (FCP 10%, Speed Index 10%, LCP 25%, TBT 30%, CLS 25%); each sub-score maps from a raw metric value through a log-normal curve, which this composer takes as its input rather than recomputing. Real scores depend on your throttling mode and run-to-run variance, so read the median of several runs under a fixed device and network preset before you trust a number.

Drag each metric score from 0 to 1 to see the Performance score and the points still available per metric, largest first. Defaults are the worked example (assumption values). Modelled, not measured.

Then sort your own metrics by that number. The top row names the first fix, whatever the colours in the report suggest.

Why does the Lighthouse score change between runs?#

On Lighthouse's mobile TBT curve as of October 2026, for example, 300 ms scores 0.79 and 400 ms scores 0.68, so small main-thread noise moves the total. On that curve, an extra tenth of a second of blocking costs about 3 points at a 30 percent weight. Because the middle of each curve is steep, small noise lands as visible points.

Show data table
Mobile TBT scoring curve; arithmetic from the GoogleChrome Lighthouse source read 2026-10-09
Stage Value
0 ms 1
100 ms 0.98
200 ms 0.9
300 ms 0.79
400 ms 0.68
500 ms 0.58
600 ms 0.5
700 ms 0.42
800 ms 0.36
900 ms 0.31
1000 ms 0.27
1100 ms 0.23
1200 ms 0.2

Between 300 and 400 ms the score falls from 0.79 to 0.68, so small noise shows.

Figure Mobile TBT scoring curve; arithmetic from the GoogleChrome Lighthouse source read 2026-10-09 GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09

Google's variability guide names where the noise comes from: local networks, web servers, client hardware, other apps on the machine and the browser itself. For that reason, it gives one rule: "The median Lighthouse score of 5 runs is twice as stable as 1 run." So to monitor Lighthouse scores over time, compare medians and treat a gap of a few points as noise.

Why do mobile and desktop score the same page differently?#

Desktop runs use their own curves, so the worked example's raw values that score 73 on mobile curves score 42 on desktop, where LCP reaches 0.90 at 1200 ms. In the GoogleChrome Lighthouse source as of October 2026, every desktop control point is tighter than its mobile one, apart from CLS. The Chrome scoring docs give the history. Before Lighthouse v6, desktop runs used the mobile curves, which "led to artificially inflated desktop scores".

Desktop control points, in milliseconds (CLS unitless). Source: the five metric audit files in GoogleChrome/lighthouse, main branch read 2026-10-09

metricvalue that scores 0.90 (p10)value that scores 0.50 (median)
FCP9341600
Speed Index13112300
LCP12002400
TBT150350
CLS0.10.25
The worked example's raw values on two sets of curvessame raw values

73

Mobile curves

42

Desktop curves

The same five raw values score far lower on the tighter desktop curves.

The worked example's raw values on two sets of curves (Performance score, 0 to 100 (assumption values))
OptionPerformance score, 0 to 100 (assumption values)
Mobile curves73
Desktop curves42

Source: arithmetic from GoogleChrome/lighthouse metric audits, main branch read 2026-10-09. Modelled, not measured

In short, a mobile score and a desktop score are two different tests. Compare each one only with itself.

What does a lab run simulate, and why does field data disagree?#

A default mobile lab run in the Lighthouse throttling docs, read October 2026, simulates 150 ms latency, 1.6 Mbps down and a 4x CPU slowdown on one device. The throttling docs also set 750 Kbps up. In short, that is one device on one network, every time.

Default mobile throttling. Source: GoogleChrome/lighthouse throttling.md, main branch read 2026-10-09

Itemas printed per setting
round-trip latency (ms)150
download throughput (Mbps)1.6
upload throughput (Kbps)750
CPU slowdown multiplier4

Field data is the opposite, since it comes from real people on real devices. In web.dev's lab and field article, last updated 2022-07-18, CrUX shows real Chrome users "over a 28-day period". By contrast, a lab report will "return the same LCP element every single time", the article says, while real users see different ones. Also, TBT is only a lab stand-in for Interaction to Next Paint (INP), which needs real clicks and taps.

Which Lighthouse pages do you build a score gate from?#

Build a score gate from three GoogleChrome pages read in October 2026, the scoring docs, the variability guide and the Lighthouse CI configuration reference. Read them in this order:

  1. Lighthouse performance scoring: the weights and the colour bands your threshold is set against.
  2. Score variability: judge the median of 5 runs, not one run.
  3. Lighthouse CI configuration: numberOfRuns, the assert block and its aggregation methods.

Also pin the Lighthouse version. In Lighthouse 8, the same scoring docs list Time to Interactive at 10 percent and CLS at 15 percent. As a result, a version bump can move the score with no code change.

Show data table
Lighthouse 8 weights beside the Lighthouse 10 weights on the five shared metrics (Lighthouse 8 also gave Time to Interactive 10), percent of the Performance score; Source: Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19, and GoogleChrome/lighthouse default-config.js, main branch read 2026-10-09
Dimension Lighthouse 8 Lighthouse 10
FCP 10% 10%
SI 10% 10%
LCP 25% 25%
TBT 30% 30%
CLS 15% 25%

Between Lighthouse 8 and 10, CLS rose from 15 to 25 percent of the score.

Figure Lighthouse 8 weights beside the Lighthouse 10 weights on the five shared metrics (Lighthouse 8 also gave Time to Interactive 10), percent of the Performance score; Source: Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19, and GoogleChrome/lighthouse default-config.js, main branch read 2026-10-09 Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19; GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09

What is a good score, and how do you gate it in CI?#

Chrome for Developers marks 90 to 100 (green) as good and 50 to 89 (orange) as needing improvement, in its scoring docs last updated 2019-09-19. Anything from 0 to 49 is poor. Since one run is noisy, the gate below asserts 0.9 on the most typical of five runs. The Lighthouse CI docs call that method median-run.

Show data table
The worked example's score against the Lighthouse bands: poor below 50, needs improvement from 50, good from 90; Source: Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19
Measure Value Target Range
Worked example, mobile 73 score 90 score 0 score to 100 score

The worked example's 73 sits in the orange band, below the green edge at 90.

Figure The worked example's score against the Lighthouse bands: poor below 50, needs improvement from 50, good from 90; Source: Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19 Chrome for Developers, Lighthouse performance scoring, last updated 2019-09-19
lighthouserc.js · javascript
// lighthouserc.js: collect five runs, then assert on the representative one
module.exports = {
  ci: {
    collect: {
      staticDistDir: './dist',
      numberOfRuns: 5,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', {minScore: 0.9, aggregationMethod: 'median-run'}],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

First, lhci autorun runs collect, assert and upload in turn. Then the error level fails the pull request below 90. Also add a budget, because an assertion judges a score while a budget judges resource sizes and request counts. For instance, a bundle can grow while the rounded score still reads green.

When not to chase a higher Lighthouse score?#

Past a score near 0.96 each point costs more, and the Chrome scoring docs, last updated 2019-09-19, say 99 to 100 takes the metric gain of 90 to 94. The same page calls a perfect 100 "extremely challenging to achieve and not expected". Three cases call for a different tool:

  • Your metrics already sit past the knee of the curve. Instead of another lab point, spend the time on field monitoring.
  • Your field data already passes Core Web Vitals. Then real users are fine, and the lab score is a diagnostic, not a goal.
  • You want to compare your site with a rival's. Instead, compare field metrics, because Lighthouse scores from different runs and versions do not line up.
Show data table
Mobile LCP scoring curve; arithmetic from the GoogleChrome Lighthouse source read 2026-10-09
Stage Value
1000 ms 1
1500 ms 1
2000 ms 0.97
2500 ms 0.9
3000 ms 0.78
3500 ms 0.64
4000 ms 0.5
4500 ms 0.37
5000 ms 0.27
5500 ms 0.19
6000 ms 0.13

Near the top the curve flattens, so each extra point costs more work.

Figure Mobile LCP scoring curve; arithmetic from the GoogleChrome Lighthouse source read 2026-10-09 GoogleChrome/lighthouse on GitHub, main branch read 2026-10-09

What should you measure after the lab score?#

Core Web Vitals judge field data at the 75th percentile, with good LCP within 2.5 seconds per web.dev, last updated 2024-10-31, so measure that next. The same Web Vitals page sets good INP at 200 milliseconds or less and good CLS at 0.1 or less.

Core Web Vitals good thresholds at the 75th percentile. Source: web.dev (Google), Web Vitals, last updated 2024-10-31

Itemgood threshold at the 75th percentile of page loads
LCP (seconds)2.5
INP (milliseconds)200
CLS (unitless)0.1

For what each metric measures, read what Web Vitals are. Then the site optimization guide for 2026 picks up the field side of speed work. If a team wants the metrics fixed, our web performance work covers it. Likewise, our CI and CD pipeline work covers wiring the gate. Even so, the three Google pages above are enough on their own to rebuild every number here.

Questions this post answers

Why does the Lighthouse performance score change between runs?
Because the middle of each curve is steep, small noise lands as visible points. So to monitor Lighthouse scores over time, compare medians and treat a gap of a few points as noise.
What is the difference between Lighthouse lab data and field data?
A default mobile lab run in the Lighthouse throttling docs, read October 2026, simulates 150 ms latency, 1.6 Mbps down and a 4x CPU slowdown on one device. Field data is the opposite, since it comes from real people on real devices. Also, TBT is only a lab stand-in for Interaction to Next Paint (INP), which needs real clicks and taps.
What is a good Lighthouse performance score?
Chrome for Developers marks 90 to 100 (green) as good and 50 to 89 (orange) as needing improvement, in its scoring docs last updated 2019-09-19. Anything from 0 to 49 is poor.

Keep reading