Reading audits and insights
Turn a Lighthouse report into a short, ordered list of fixes. The store's report, ordered the way Lighthouse orders it, and three of its fixes measured: estimated savings against what changed.
- 01Read the report's orderSay why one fix sits above another, and narrow the list to one metric with the filter.
- 02Treat savings as estimatesFind the exact file behind an insight, measure each fix, and explain why the estimates don't add up.
- 03Look past a passUse the treemap to see where your JavaScript is, and spot a pass that still hides a problem.
From report to fix list
Lighthouse gave Leaf and Kettle a Performance score of 90. Asha's phone is still slow. So you scroll down past the score, looking for something to fix. The report keeps going. It lists checks with names like "Render-blocking requests". Some come with an estimate in milliseconds, some with a size. Further down, a folded section holds the checks that passed.
Two questions come up at once. Which one do you fix first? And if you fix it, how much faster will the page get? This lesson answers both, with one report. The course ran Lighthouse on the store with its default settings and kept the whole report. Then it made the three fixes at the top of the list, and measured each one.
Here is the top of that list:
// The first three insights in the store's report, each with the estimate it shows.const topThree = [ ["Document request latency", "Est savings of 600 ms"], ["Improve image delivery", "Est savings of 83 KiB"], ["Render-blocking requests", "Est savings of 1,190 ms"],]; topThree.forEach(([title, savings], index) => console.log(`${index + 1}. ${title}: ${savings}`));- Lines 3 to 5 hold the first three entries in the report, in its order. Each is an insight: a check that points at a likely cause of a slow page. Next to each is the estimate the report shows.
- Line 8 prints one numbered line for each:
1. Document request latency: Est savings of 600 ms,2. Improve image delivery: Est savings of 83 KiBand3. Render-blocking requests: Est savings of 1,190 ms.
"Est savings" is short for estimated savings: the report's guess at how much one fix could save. Now look at the order. The 600 ms comes before the 1,190 ms. The one in the middle isn't even a time: a KiB is a unit of size. So the list isn't sorted by the biggest number. What is it sorted by? And if you made all three fixes, would the page really get faster by 600 ms plus 1,190 ms? The sections on diagnostics and on savings answer both.
The whole report at once
Lighthouse calls each check an audit, as How Lighthouse works explained. Below the score, the report sorts its audits into four parts:
- Metrics: the five numbers from The Lighthouse score. They're the only audits that count in the score.
- Insights: checks that point at a likely cause. The store's report lists 8 to look at: 6 that failed, and 2 that only show information.
- Diagnostics: more checks, with more information. The store's report lists 3.
- Passed audits: the 18 checks that found nothing, folded shut at the bottom.
The home inspector from How Lighthouse works has finished. Their report has a short list on the front page: "Fix these first", worst problem at the top. Behind it come pages of other findings, things worth knowing. At the back sits the full checklist, every passed box ticked and filed. You read the front page first. You turn to the back only when you need to.
- In real life: The front page: "Fix these first"
- In Lighthouse: The insights that didn't pass, at the top
- In real life: The pages of other findings
- In Lighthouse: The diagnostics
- In real life: The ticked boxes, filed at the back
- In Lighthouse: Passed audits, folded shut at the bottom
- In real life: "Worst problem first"
- In Lighthouse: The report's own order, worked out from its estimates
The report hands you a list of leads, sorted by how much each fix could raise the score. Each estimate is a guess about one fix on its own. So fix one thing, run Lighthouse again, and measure. And read a pass as "this test found nothing", not "nothing is wrong".
Here is the store's whole list, in the report's order. The buttons along the top are the report's own filter. The section on diagnostics explains them.
Lowest scores come first. Among equal scores, the fix that would raise the Performance score most comes first. Document request latency is at the top: its estimate would add about 3.7 points. The 18 passed audits are folded away at the bottom.
Insights, which replaced the old audits
Insights should look familiar. In Performance insights, they were the warning lights on DevTools' dashboard. Each one points at a likely cause, and you check it yourself. Lighthouse shows the same insights. Its report says so right above the list: "These insights are also available in the Chrome DevTools Performance Panel".
That's new. Older versions of Lighthouse had their own checks for the same problems, with different names. Lighthouse 13 merged the two. Its release notes say: "we have consolidated our Lighthouse performance audits and DevTools Performance panel insights into common performance insights available in both tools." The old audits are gone from the report.
Often, one insight took over several old audits. Document request latency took over three:
redirects, which looked for detours to another address;server-response-time, which timed how fast the server started to answer;uses-text-compression, which checked that text was sent squeezed smaller.
The score didn't change. The release notes again: "There are no changes to the performance scoring in this version of Lighthouse, which is based on the metrics rather than the audits." That's the school report from The Lighthouse score. Insights are the teacher's comments in the margin. They weigh 0.
The store's report holds 17 insights. Six failed. Two only showed information. The other nine passed or didn't apply to this page.
One insight, several checks
So what's inside an insight that took over three audits? A checklist, with a box for each of their jobs. Here is Document request latency's checklist for the store's HTML:
// Document request latency's checklist for the store's HTML.const checklist = [ ["Avoids redirects", true], ["Server responded slowly (observed 704 ms)", false], ["Applies text compression", true],]; for (const [label, passed] of checklist) console.log(`${passed ? "Passed" : "Failed"}: ${label}`);- Lines 3 to 5 hold the three boxes, as the report recorded them. Each has a label, and whether it passed.
- Line 8 prints each box:
Passed: Avoids redirects,Failed: Server responded slowly (observed 704 ms)andPassed: Applies text compression.
If any box fails, the whole insight fails. That happened here. The failed box tells you which job to look at: the server. It took 704 ms to start answering. That's the slow server the course gave the store in Performance insights.
LCP request discovery has a checklist too. On the store, it failed on one box: "fetchpriority=high should be applied", for the photo of mint tea. fetchpriority="high" is an attribute that asks the browser to fetch an image at a higher priority than other images. The photo is the page's largest piece of content, so it should arrive early.
Which file? Read the table
A checklist tells you which job failed. Some insights go one step further, with a table that names the exact file.
The inspector's front page says "Damp". Worth knowing, but which room? You turn the page and find a table: back bedroom, north wall, three patches. Now you know where to start. You don't repaint the whole house.
- In real life: "Damp" on the front page
- In Lighthouse: The insight's title, like Render-blocking requests
- In real life: The table: which room, which wall
- In Lighthouse: The insight's table: each file's address, or an element like
img#hero - In real life: How bad it is in each room
- In Lighthouse: The numbers in each row: a time, or a size in bytes
Here is the table for Render-blocking requests. A render-blocking request is a file the browser must have before it paints anything, like a stylesheet in the page's <head>.
// Render-blocking requests: its table, one row per file the first paint waited for.const rows = [ { file: "app.js", ms: 1202 }, { file: "styles.css", ms: 152 }, { file: "fonts.css", ms: 302 },]; for (const row of rows) console.log(`${row.file} took ${row.ms} ms to download.`);- Lines 3 to 5 hold the table's three rows: a file, and the number in its Duration column.
- Line 8 prints
app.js took 1202 ms to download.,styles.css took 152 ms to download.andfonts.css took 302 ms to download.
These times don't come from the real load. In the insight's source, the Duration is each file's download time in Lantern, the simulation of a slow phone from Throttling and simulation. Still, the table answers "which room". Three files, all named in the <head> of the store's index.html. The script app.js is by far the slowest.
Some tables name an element, not only a file. Improve image delivery points at img#hero, the photo, with the reason: "This image file is larger than it needs to be (1600x1067) for its displayed dimensions (665x443)." The file is 1,600 pixels wide. The phone shows it 665 pixels wide.
So when an insight feels vague, open it and read its table. It names the file or element to change.
Diagnostics
Below the insights comes the second list. The report describes it in one line: "More information about the performance of your application. These numbers don't directly affect the Performance score." They're the inspector's other findings, like "the boiler is fifteen years old": not always a fault, but worth knowing.
The store's report has 12 diagnostics. Three made the list:
- Image elements do not have explicit width and height. The photo's
<img>tag gives no size. - Minify JavaScript, with "Est savings of 21 KiB". To minify a file is to strip its spaces and shorten its names. It gets smaller and still does the same thing.
- Avoid long main-thread tasks, which said "3 long tasks found". It only informs: it has no pass or fail.
The first one stayed a diagnostic on purpose. Lighthouse 13's release notes say it was kept "as a separate diagnostic audit to help identify those that don't cause CLS issues as well." In plain words, it lists every image without a size, even one that didn't make anything jump.
Insights and diagnostics are easy to mix up. Read each row of this table as a question, answered for each list:
| Question | Insights | Diagnostics |
|---|---|---|
| Do they count in the score? | No. They weigh 0. | No. They weigh 0. |
| Does DevTools show them too? | Yes. The Performance panel shows the same insights. | No. They're Lighthouse's own audits. |
| What do they give you? | A likely cause, with a checklist or a table. | More information, sometimes with an estimate. |
| One from the store's report | Render-blocking requests. | Minify JavaScript. |
How the report orders the list
Back to the first question. Why did a 600 ms estimate come before a 1,190 ms one? The report's code sorts each list in three steps:
- Lowest score first. Each audit has a score from 0 to 1. Failed ones score 0, so they come first.
- Then the biggest gain. Among equal scores, the report works out how many points each estimate would add to the Performance score. Its code calls this "the estimated improvement to the performance score". It multiplies those points by a guidance level: a number for how much help Lighthouse gives you to fix the problem. Higher means more help.
- Then guidance alone. Audits with no estimated gain are sorted by their guidance level. (If two gains both come to 0, it first tries a rougher, straight-line version of the gain.)
Step 2 decided the store's top three. Here is its sum for each:
// What each top insight's estimate would add to the score, in points.// A metric's score runs from 0 to 1. FCP weighs 10 points, LCP weighs 25.const documentLatency = (0.98 - 0.86) * 10 + (0.96 - 0.86) * 25;const imageDelivery = (0.92 - 0.86) * 25;const renderBlocking = (1 - 0.86) * 10; console.log(`Document request latency: ${documentLatency.toFixed(1)} points`);console.log(`Improve image delivery: ${imageDelivery.toFixed(1)} points`);console.log(`Render-blocking requests: ${renderBlocking.toFixed(1)} points`);- Line 2 is a reminder from The Lighthouse score. Each metric gets a score from 0 to 1. FCP counts for 10 points of the 100, and LCP for 25.
- Line 3 is Document request latency. Its estimate is 600 ms for both FCP and LCP. FCP would drop from 1,939 ms to 1,339 ms, and its score would rise from 0.86 to 0.98. LCP would drop from 2,689 ms to 2,089 ms, and its score would rise from 0.86 to 0.96.
- Line 4 is Improve image delivery. Its text showed bytes, but it also carries a time estimate: 300 ms of LCP. LCP's score would rise from 0.86 to 0.92.
- Line 5 is Render-blocking requests. The report showed 1,190 ms. It sorts by the estimate rounded to the nearest 50 ms, which is 1,200. FCP's score would rise from 0.86 all the way to 1.
- Lines 7 to 9 print
Document request latency: 3.7 points,Improve image delivery: 1.5 pointsandRender-blocking requests: 1.4 points.
So the report ranks points, not milliseconds. Render-blocking requests saves the most time, but only on FCP, which counts for 10. And FCP's score can't go past 1, however much sooner it comes. The photo's 300 ms is on LCP, which counts for 25. It's the school report again: a better mark in a subject that counts more lifts the overall mark more. All three had the same guidance level, 3, so it didn't change their order.
Read the top of the list as "the biggest gain in score, if the estimates are right". It isn't "the most time saved".
Filtering by metric
Above the lists, the report has a row of buttons headed "Show audits relevant to:". On the store, they read All, FCP, LCP, TBT and CLS. Pick a metric, and the report hides every audit that has no estimate for it. Lowest scores still come first. After that, the biggest estimate for that metric comes first. Try it in the playground at the top of this lesson.
Estimated savings and why they don't add up
Now the second question. If you made all three fixes, how much faster would the page get? To answer it, you need to know what an estimate is, and what it isn't.
A builder quotes you for insulating the loft. At the bottom it says: "Could save about 300 a year on heating." That's the builder's own sum, for that one job, in an average winter. It isn't a promise. Some quotes give the saving in money, some in units of gas. Only next winter's bills show what you really saved.
- In real life: "Could save about 300 a year"
- In Lighthouse: "Est savings of 600 ms"
- In real life: The builder's own sum, for that one job
- In Lighthouse: The insight's own estimate, for that one fix
- In real life: A saving in money, or in units of gas
- In Lighthouse: A saving in milliseconds, or in bytes
- In real life: Next winter's heating bills
- In Lighthouse: Running Lighthouse again, after the fix
Where an estimate comes from
Each insight works out its estimate its own way. Two from the store:
- Document request latency does a simple sum. The server took 704 ms. The insight aims for 100 ms. 704 minus 100 is 604 ms, shown as 600 ms.
- Render-blocking requests asks Lantern. It simulates the first paint twice: once as the page is, and once with the three files taken out of the way. The difference is the estimate.
The text rounds times to the nearest 10 ms. The number the report sorts by is rounded to the nearest 50 ms. And some estimates are sizes, not times. Improve image delivery showed bytes:
// Improve image delivery's estimate for the photo, in bytes.const wastedBytes = 85051;const kib = wastedBytes / 1024; console.log(`Est savings of ${Math.round(kib)} KiB`);- Line 2 comes from the insight's table: 85,051 bytes of the photo could go.
- Line 3 divides by 1,024. A KiB, a kibibyte, is 1,024 bytes.
- Line 5 prints
Est savings of 83 KiB, the text in the report.
When an insight has bytes to save, its text shows the bytes, even if it has a time estimate too. The photo's 300 ms of LCP never appears in the text. Yet that's the number the report sorted by.
Three fixes, measured
The course made the three fixes at the top of the list, one at a time:
- The server fix: the server sends the HTML at once, without its 700 ms wait.
- The render-blocking fix: both stylesheets move inside the HTML, in a
<style>element. And app.js getsdefer, so it runs after the page has been read. - The photo fix: the photo is resized to 665 pixels wide. It shrinks from 102,809 bytes to 8,996.
It ran Lighthouse three times on each version, taking turns, and three times on all three fixes together. Each fix made its own insight pass. Here is the render-blocking fix:
// The render-blocking fix, on the first paint (FCP), in ms.const estimated = 1200; // what the report sorts byconst before = 1937.8; // the store, median of three runsconst after = 1785.9; // with the fix, median of three runs console.log(`The report estimated ${estimated} ms sooner.`);console.log(`The first paint came ${Math.round(before - after)} ms sooner.`);- Line 2 is the estimate the report sorts by.
- Lines 3 and 4 are recorded: the first paint before and after the fix. Each is the median of three runs: the middle value once you sort them.
- Line 6 prints
The report estimated 1200 ms sooner.Line 7 printsThe first paint came 152 ms sooner.
The fix worked, but it saved about an eighth of the estimate. The estimate and the fix weren't the same change. The estimate simulated the page with the three files gone. The real fix still sent the styles, inside the HTML. It still ran app.js, only later. Only measuring showed what that was worth.
The photo fix went the other way. Its estimate was 300 ms of LCP, and LCP came 451 ms sooner. Pick each version below to see its runs:
The store as it is, with a median score of 90. The report's top three estimates: Document request latency, Est savings of 600 ms; Render-blocking requests, Est savings of 1,190 ms; Improve image delivery, Est savings of 83 KiB.
This picture puts each estimate beside what was measured:
Why the server fix saved nothing
The server fix is the odd one. The report put it first, at 600 ms. Three runs later, the first paint hadn't moved. At the median, it came 1 ms later. Did the fix fail? No: the insight passed afterwards. The answer is in how Lantern imitates a slow phone.
Lantern doesn't replay each file's real wait. It gives every request to an origin one server time: the median of the times it saw from that origin. An origin is the scheme, host and port together, like http://127.0.0.1:8080. The store's HTML was its only slow file. Every other file came back at once. So the median was tiny: 2.2 ms, as the hidden Server Backend Latencies audit in the result shows.
So the simulated load never waited for the slow server at all. Taking the wait away changed nothing in the simulation. Meanwhile the insight read the real 704 ms from the load, and estimated 600 ms. Each was right about what it looked at.
The course also tried DevTools throttling: the applied throttling from Throttling and simulation, which really slows Chrome down. There, the same fix saved 140 ms of the first paint. Still not 600. Lighthouse's docs on variability say DevTools throttling "partially masks these effects by applying a minimum request latency in its network throttling." Every request already waits a set minimum time, so most of a slow server hides inside that wait.
So when a fix moves nothing, don't give up on it at once. Check what the simulation saw. Then measure again, with DevTools throttling or in the Performance panel.
Why the estimates don't add up
Now add the three estimates up, and compare them with all three fixes made together:
// Each fix's estimate, added up, against all three fixes measured together, in ms.const estimates = { FCP: [600, 1200, 0], LCP: [600, 0, 300] }; // server, render-blocking, photoconst before = { FCP: 1937.8, LCP: 2689.4 }; // the store, median of three runsconst after = { FCP: 1786.7, LCP: 2236.7 }; // all three fixes, median of three runs for (const metric of ["FCP", "LCP"]) { const addedUp = estimates[metric].reduce((sum, ms) => sum + ms, 0); const saved = Math.round(before[metric] - after[metric]); console.log(`${metric}: the estimates add up to ${addedUp} ms. All three fixes saved ${saved} ms.`);}- Line 2 holds each fix's estimate for FCP and for LCP, in the order server, render-blocking, photo.
- Lines 3 and 4 are recorded: the store, and the store with all three fixes, each the median of three runs.
- Line 7 adds up a metric's three estimates. Line 8 works out what was really saved.
- Line 9 prints
FCP: the estimates add up to 1800 ms. All three fixes saved 151 ms.andLCP: the estimates add up to 900 ms. All three fixes saved 453 ms.
Added up, the estimates promised 1,800 ms of FCP. All three fixes together saved 151, about what the render-blocking fix saved alone. Each insight works out its estimate against the page as it is now, as if its fix were the only one. And two fixes can speed up the same wait. It's like fitting new windows and thick curtains: each one stops a draught, but fit both and you don't save twice.
All three together
The combined version brought one more surprise. It was faster, and it jumped more. Its CLS, Cumulative Layout Shift, measures content moving on its own. It reached 1.0757 in 2 of its 3 runs. The store's was 0.1364. For the biggest shift, Lighthouse blamed two causes: "Media element lacking an explicit size" and "Web font loaded".
The photo had no size all along, and a diagnostic had said so. Together, the three fixes made that old problem much worse. CLS counts for 25 points, so the median score fell from 90 to 73. Three fixes the report asked for, and the score went down. That's why you measure after each change, and again when you put the changes together.
The treemap view
The lists tell you about time. They don't show you what's inside your JavaScript. For that, the report offers a View Treemap button, when its result holds data about the page's scripts. Click it, and Lighthouse opens a separate page: its treemap app.
A treemap is a picture that splits a rectangle into boxes, one per item, each sized by how big the item is. Here each box is a script, sized by its bytes. The app also shows which of those bytes went unused: code that didn't run while Lighthouse loaded the page. If your scripts come with source maps, files that map bundled code back to the files it was built from, each bundle splits into those files.
Your suitcase is too heavy. You tip it out onto the bed and lay each thing flat. Now you can see it: the winter coat covers half the bed. A few shirts never left the case all week. The bed doesn't tell you what to leave at home. It shows you where the weight is.
- In real life: The suitcase
- In Lighthouse: The page's JavaScript
- In real life: Each thing laid flat, taking the space it takes
- In Lighthouse: Each script drawn as a box, sized by its bytes
- In real life: Clothes you packed but never wore
- In Lighthouse: Unused bytes: code that didn't run during the load
- In real life: The coat that covers half the bed
- In Lighthouse: The biggest box in the treemap
Here is the store's data, the numbers its treemap draws:
// The store's scripts in the treemap's data: their bytes, and the bytes Lighthouse found unused.const scripts = { "/app.js": [153208, 220], "/reviews.js": [517, 0], "/widget.js": [797, 0] };const total = Object.values(scripts).reduce((sum, [bytes]) => sum + bytes, 0); for (const [name, [bytes, unused]] of Object.entries(scripts)) { console.log(`${name}: ${((bytes / total) * 100).toFixed(1)}% of the JavaScript, ${((unused / bytes) * 100).toFixed(1)}% unused`);}- Line 2 is recorded: each script's bytes, and how many of them went unused.
- Line 3 adds up all the bytes.
- Line 6 prints one line per script:
/app.js: 99.1% of the JavaScript, 0.1% unused,/reviews.js: 0.3% of the JavaScript, 0.0% unusedand/widget.js: 0.5% of the JavaScript, 0.0% unused.
So the coat is app.js. It's nearly all of the store's JavaScript, and nearly all of it ran. That's why "Reduce unused JavaScript" passed. app.js is mostly the tea catalog. The treemap shows where the bytes are. Whether they should be there is your call.
Open the store's report (1.0 MB) to try its filter and its View Treemap button yourself.
Passed audits that still hide problems
At the bottom of the report sits the folded section, Passed audits. On the store, it held 18. It's tempting to skip it. But a pass means only one thing: one test, with its own rules, found nothing.
The inspector checks every room for damp, except rooms the owner marked "Private". This owner marked every room Private. So the damp box gets a tick: no damp found, in no rooms at all. They also write down only cracks wider than 3 mm. A dozen 2 mm cracks still get a tick.
- In real life: A room marked "Private"
- In Lighthouse: A file sent with
Cache-Control: no-store - In real life: A tick for damp, with no room checked
- In Lighthouse: The cache insight passing with an empty table
- In real life: Only cracks wider than 3 mm are written down
- In Lighthouse: Forced reflows count only from 30 ms in one task
- In real life: A tick in the box
- In Lighthouse: A passed audit
Three of the store's passes hid something.
A cache check with nothing to check
Use efficient cache lifetimes checks how long the browser may keep each file. A cache lifetime is how long a saved copy stays usable, so a repeat visit can skip the download. The store's server sent every file with Cache-Control: no-store, a header that says "never keep a copy". Nothing was cached at all. Still, the insight passed, with an empty table.
Its source explains. It skips every file whose headers turn caching off: no-store, no-cache, must-revalidate or private. The comment reads: "If we have any of these, the user intentionally doesn't want to cache." Every file was skipped, so nothing was left to fail.
So if your server turns caching off by mistake, this insight won't tell you. Check the response headers in the Network panel.
A rule with a line in it
Forced reflow looks for scripts that make the browser lay out the page on the spot. Performance insights caught the store's reviews doing this. The insight doesn't flag every case. Here is its rule:
// Forced reflow's rule: add up the forced reflows in one task. Flag them from 30 ms.const THRESHOLD_MS = 30;const reflowMs = 76.4; // the store's reviews, recorded with the CPU 4 times slower const verdict = reflowMs >= THRESHOLD_MS ? "flagged" : "passed";console.log(`${reflowMs} ms of forced reflow in one task: ${verdict}.`);- Line 2 is the line, from DevTools' source: 30 ms of forced reflow in one task.
- Line 3 is the time Performance insights recorded for the store's reviews, with the CPU 4 times slower.
- Line 5 compares the two. Line 6 prints
76.4 ms of forced reflow in one task: flagged.
In Lighthouse, with the default settings, Forced reflow passed. The default loads the page at your machine's full speed, then simulates the slow phone afterwards, as Throttling and simulation showed. Insights read that fast load. There, the reviews' reflows stayed under 30 ms in every task. With DevTools throttling, which really slows the CPU, the same insight failed.
So a pass on a fast machine can hide work that's slow on a real phone.
A pass with an estimate
JavaScript execution time passed, at 0.3 s. Yet it still carried an estimate: 100 ms of TBT, Total Blocking Time, from The Lighthouse score. A pass can come with a saving attached. The report just doesn't put it in the list.
And a whole page can pass. In The Lighthouse score, a fast version of the store scored 100, and its Add to cart click still took 302 ms.
So before you trust a pass, ask three questions:
- Did the test skip anything? The cache insight skipped every file.
- Where is its line, and which load did it read? Forced reflow read the fast load.
- Does it still carry an estimate? JavaScript execution time did.
Insight or diagnostic?
Every audit below appeared in the store's report. Sort each one into the list it appeared in. Remember: insights are the ones the Performance panel shares.
- Render-blocking requests
- Document request latency
- Use efficient cache lifetimes
- Image elements do not have explicit width and height
- JavaScript execution time
- Avoid long main-thread tasks
Sort each audit by the list the store's report showed it in.
Common mix-ups
- "Estimated savings add up." No. The store's FCP estimates added up to 1,800 ms. All three fixes together saved 151.
- "The insight at the top saves the most time." No. The top has the biggest gain in score points, by its own estimate. Document request latency came first, and saved nothing in Lighthouse's simulation.
- "Fixing insights raises the score." No. Insights weigh 0. Only a metric that moves can move the score. All three fixes took the store's median from 90 down to 73.
- "A passed audit means no problem." No. Use efficient cache lifetimes passed, and nothing was cached at all.
- "Diagnostics count toward the score." No. Only the five metrics do.
- "Est savings of 83 KiB is a time." No. It's a size: 83 KiB is about 85,000 bytes. The photo's time estimate, 300 ms of LCP, isn't in the text.
Practice exercises
Answer first, then use the hints.
In another of the store's runs, Improve image delivery estimated 450 ms of LCP, not 300. With that estimate, what does the second line print?
// Each fix's estimate, added up, against all three fixes measured together, in ms.
const estimates = { FCP: [600, 1200, 0], LCP: [600, 0, 450] }; // server, render-blocking, photo
const before = { FCP: 1937.8, LCP: 2689.4 }; // the store, median of three runs
const after = { FCP: 1786.7, LCP: 2236.7 }; // all three fixes, median of three runs
for (const metric of ["FCP", "LCP"]) {
const addedUp = estimates[metric].reduce((sum, ms) => sum + ms, 0);
const saved = Math.round(before[metric] - after[metric]);
console.log(`${metric}: the estimates add up to ${addedUp} ms. All three fixes saved ${saved} ms.`);
}LCP: the estimates add up to 1050 ms. All three fixes saved 453 ms. The estimate moved between two runs of the same page. What was measured didn't.
In the first playground, pick FCP. Which insight comes first now?
Render-blocking requests. Its FCP estimate, 1,200 ms, is the biggest. With All picked it came third, because then the report sorts by score points.
In the second playground, which single fix gave the best median score?
The photo fix: a median score of 92, the only single fix above the store's 90. It moved LCP the most, and LCP counts for 25 points.
Suppose a faster CPU did the same reflows in 24 ms. What does the program print?
// Forced reflow's rule: add up the forced reflows in one task. Flag them from 30 ms.
const THRESHOLD_MS = 30;
const reflowMs = 24; // suppose a faster CPU did the same work in 24 ms
const verdict = reflowMs >= THRESHOLD_MS ? "flagged" : "passed";
console.log(`${reflowMs} ms of forced reflow in one task: ${verdict}.`);24 ms of forced reflow in one task: passed. The same kind of work, just under the line. That's how a fast machine can pass work that's slow on a phone.
Suppose Improve image delivery estimated 600 ms of LCP, like Document request latency. LCP would then read 0.96 on its curve. What does the second line print?
// What each top insight's estimate would add to the score, in points.
// A metric's score runs from 0 to 1. FCP weighs 10 points, LCP weighs 25.
const documentLatency = (0.98 - 0.86) * 10 + (0.96 - 0.86) * 25;
const imageDelivery = (0.96 - 0.86) * 25;
const renderBlocking = (1 - 0.86) * 10;
console.log(`Document request latency: ${documentLatency.toFixed(1)} points`);
console.log(`Improve image delivery: ${imageDelivery.toFixed(1)} points`);
console.log(`Render-blocking requests: ${renderBlocking.toFixed(1)} points`);Improve image delivery: 2.5 points. That's more than before, but still below Document request latency's 3.7, which saves on FCP as well as LCP. The order at the top wouldn't change.
Open the store's report and find Avoid long main-thread tasks. How long was the longest task, in ms?
135 ms, in reviews.js. The next one, 107 ms, came from widget.js. The table points straight at the file to open first.
Check your understanding
For each question, ask three things: what the report estimated, what was measured, and what a pass really checked.
Question 1 of 8What does the second line of the added-up example print?
Read the code, then predict// Each fix's estimate, added up, against all three fixes measured together, in ms. const estimates = { FCP: [600, 1200, 0], LCP: [600, 0, 300] }; // server, render-blocking, photo const before = { FCP: 1937.8, LCP: 2689.4 }; // the store, median of three runs const after = { FCP: 1786.7, LCP: 2236.7 }; // all three fixes, median of three runs for (const metric of ["FCP", "LCP"]) { const addedUp = estimates[metric].reduce((sum, ms) => sum + ms, 0); const saved = Math.round(before[metric] - after[metric]); console.log(`${metric}: the estimates add up to ${addedUp} ms. All three fixes saved ${saved} ms.`); }Choose an answer to see the explanation.
Question 2 of 8What does the points example print for Improve image delivery, on its second line?
Read the code, then predict// What each top insight's estimate would add to the score, in points. // A metric's score runs from 0 to 1. FCP weighs 10 points, LCP weighs 25. const documentLatency = (0.98 - 0.86) * 10 + (0.96 - 0.86) * 25; const imageDelivery = (0.92 - 0.86) * 25; const renderBlocking = (1 - 0.86) * 10; console.log(`Document request latency: ${documentLatency.toFixed(1)} points`); console.log(`Improve image delivery: ${imageDelivery.toFixed(1)} points`); console.log(`Render-blocking requests: ${renderBlocking.toFixed(1)} points`);Choose an answer to see the explanation.
Question 3 of 8What does the first line of the treemap example print?
Read the code, then predict// The store's scripts in the treemap's data: their bytes, and the bytes Lighthouse found unused. const scripts = { "/app.js": [153208, 220], "/reviews.js": [517, 0], "/widget.js": [797, 0] }; const total = Object.values(scripts).reduce((sum, [bytes]) => sum + bytes, 0); for (const [name, [bytes, unused]] of Object.entries(scripts)) { console.log(`${name}: ${((bytes / total) * 100).toFixed(1)}% of the JavaScript, ${((unused / bytes) * 100).toFixed(1)}% unused`); }Choose an answer to see the explanation.
Question 4 of 8Why can an insight that estimates 300 ms sit above one that estimates 1,190 ms?
Choose an answer to see the explanation.
Question 5 of 8The report's top insight estimates 600 ms. You fix it, and Lighthouse's numbers don't move. Why might that be?
Choose an answer to see the explanation.
Question 6 of 8Two insights estimate FCP savings of 1,200 ms and 600 ms. What should you expect if you fix both?
Choose an answer to see the explanation.
Question 7 of 8Forced reflow passes in your Lighthouse run but shows up in the Performance panel. Why?
Choose an answer to see the explanation.
Question 8 of 8Your site sends every file with Cache-Control: no-store, and the cache insight passes. What does that tell you?
Choose an answer to see the explanation.
Key takeaways
- Below the score come the insights, then the diagnostics, then the folded passed audits. Only the metrics count in the score.
- Insights are shared with DevTools' Performance panel, and replaced Lighthouse's old audits. Open one to find its checklist or table, which names the exact file or element.
- The report sorts by score, then by estimated gain in score points. A small LCP saving can outrank a big FCP one.
- Estimated savings are made one fix at a time, and they don't add up. Measure each fix, and measure them together.
- The treemap shows where your JavaScript bytes are, and which of them went unused.
- A pass means one test, with its own rules, found nothing. Check what it skipped, and which load it read.
Remember the one-liner.
The report gives you leads, in order. Only measuring tells you which ones paid off.
Sources
The facts on this page come from these docs and source files:
- performance-category-renderer.js, Lighthouse 13.6.0 report renderer.
- report-utils.js, what counts as passed, Lighthouse 13.6.0 report renderer.
- default-config.js, the report's groups, Lighthouse 13.6.0 source.
- What's new in Lighthouse 13, Barry Pollard and Connor Clark, Chrome for Developers.
- insight-audit.js, Lighthouse 13.6.0 source.
- DocumentLatency.ts, the server's checklist and estimate, Chrome DevTools frontend source.
- RenderBlocking.ts, the table and the estimate, Chrome DevTools frontend source.
- audit.js, rounding the savings, Lighthouse 13.6.0 source.
- format.js, milliseconds and KiB in the text, Lighthouse 13.6.0 source.
- NetworkAnalyzer.ts, Lantern's server response times, Chrome DevTools frontend source.
- ConnectionPool.ts, one server time per origin, Chrome DevTools frontend source.
- Score Variability, Lighthouse docs.
- open-tab.js, the treemap, Lighthouse 13.6.0 report renderer.
- script-treemap-data.js, Lighthouse 13.6.0 source.
- Cache.ts, the cache insight, Chrome DevTools frontend source.
- WarningsHandler.ts, forced reflows, Chrome DevTools frontend source.
Coming next: Scripting Lighthouse, also in Measuring in the lab. Run Lighthouse from Node with Puppeteer, test logged-in pages and user flows, and add custom audits.