Chrome Lighthouse Score Improve (2026)
Chrome Lighthouse provides comprehensive audits for performance, accessibility, progressive web app compliance, SEO, and best practices. A higher Lighthouse score directly correlates with better user experience, improved search rankings, and increased conversion rates. This guide covers actionable techniques to improve your Chrome Lighthouse score across all core metrics.
Understanding Lighthouse Score Categories
Lighthouse evaluates your site across five categories. Each category produces a score from 0 to 100, with 90 or above considered good. The categories are:
- Performance: Load speed and interactivity
- Accessibility: How usable your site is for all users
- Best Practices: Modern web standards compliance
- SEO: Search engine optimization factors
- PWA: Progressive Web App features (optional)
Most developers focus primarily on performance, but ignoring other categories hurts your overall score and user experience. A site that scores 95 on performance but 60 on accessibility is still a problematic site. both for users and for Lighthouse audits that feed into search visibility signals.
Here is a quick reference for score thresholds across all categories:
| Score Range | Rating | Action Required |
|---|---|---|
| 90 – 100 | Good | Maintain and monitor |
| 50 – 89 | Needs Improvement | Prioritize fixes |
| 0 – 49 | Poor | Address immediately |
The scoring weights within Performance deserve special attention. LCP, TBT (Total Blocking Time), CLS, FCP, and Speed Index each contribute differently. TBT has historically carried 30% of the performance score weight, making JavaScript optimization particularly high-use.
Improving Performance Score
Performance is usually the hardest category to optimize. It also has the most moving parts. Here are practical techniques ordered roughly by impact:
Optimize Images
Unoptimized images tank performance scores. Use modern formats like WebP or AVIF:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Descriptive alt text" loading="lazy" width="800" height="600">
</picture>
Always specify explicit width and height attributes to prevent layout shifts. The loading="lazy" attribute defers loading off-screen images until users scroll near them.
For your hero image. the one that drives LCP. do the opposite: skip lazy loading and preload it instead. Lazy loading a hero image delays LCP unnecessarily since it will always be in the initial viewport.
<!-- In <head>. preload the LCP image -->
<link rel="preload" as="image" href="hero.avif" type="image/avif">
<link rel="preload" as="image" href="hero.webp" type="image/webp">
Image compression tools worth using: Squoosh (browser-based, excellent quality control), imagemin (Node.js, good for build pipelines), and Sharp (fast server-side processing). Target file sizes under 150kb for full-width hero images, and under 50kb for inline content images.
Minimize Main Thread Work
JavaScript execution blocks rendering. Use the Chrome DevTools Performance tab to identify expensive operations, then:
- Defer non-critical scripts
- Code-split your bundles
- Remove unused JavaScript
<script src="critical.js" defer></script>
<script src="non-critical.js" defer></script>
The defer attribute executes scripts after HTML parsing completes without blocking the initial render. The async attribute is different. it downloads in parallel but executes immediately when downloaded, which can block rendering if the file is large.
Use type="module" for modern browsers, which defers by default:
<script type="module" src="app.js"></script>
For third-party scripts like analytics or chat widgets that you do not control, load them after the page becomes interactive using a facade pattern:
// Delay third-party scripts until after first interaction
function loadThirdParty() {
const script = document.createElement('script');
script.src = 'https://widget.example.com/loader.js';
script.async = true;
document.head.appendChild(script);
// Remove listeners after first load
['click', 'scroll', 'keydown'].forEach(event =>
document.removeEventListener(event, loadThirdParty)
);
}
['click', 'scroll', 'keydown'].forEach(event =>
document.addEventListener(event, loadThirdParty, { passive: true, once: true })
);
This pattern keeps your TBT low on initial load while still loading the widget for active users.
Implement Core Web Vitals
Core Web Vitals directly impact your Lighthouse performance score:
Largest Contentful Paint (LCP) measures loading performance. Target under 2.5 seconds:
<!-- Preload your hero image -->
<link rel="preload" as="image" href="hero.webp">
LCP is affected by slow server response times as much as image size. Add server-timing headers to diagnose TTFB (Time to First Byte):
// Express.js example. measure and expose timing
app.use((req, res, next) => {
const start = process.hrtime.bigint();
res.on('finish', () => {
const duration = Number(process.hrtime.bigint() - start) / 1e6;
res.setHeader('Server-Timing', `total;dur=${duration}`);
});
next();
});
Cumulative Layout Shift (CLS) measures visual stability. Reserve space for dynamic content:
/* Specify aspect ratios for images */
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
}
/* Reserve space for ads or dynamic embeds */
.ad-container {
min-height: 250px;
width: 100%;
}
/* Prevent font-related layout shifts */
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap;
size-adjust: 105%; /* Adjust to match fallback font metrics */
}
The font-display: swap value shows a fallback font immediately and swaps when the custom font loads. This prevents invisible text (FOIT) but can cause a small layout shift. Use size-adjust and ascent-override to match the fallback font’s metrics as closely as possible, minimizing the visual shift.
First Input Delay (FID) / Interaction to Next Paint (INP) measures interactivity. Lighthouse now reports INP as the primary interactivity metric. Minimize main thread blocking:
// Break long tasks into smaller chunks
function processItems(items) {
const chunkSize = 10;
let index = 0;
function processChunk() {
const end = Math.min(index + chunkSize, items.length);
for (; index < end; index++) {
// Process item
}
if (index < items.length) {
requestIdleCallback(processChunk);
}
}
requestIdleCallback(processChunk);
}
For heavy computations that cannot be chunked, offload to a Web Worker:
// main.js
const worker = new Worker('/worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (event) => {
renderResults(event.data.result);
};
// worker.js
self.onmessage = (event) => {
const result = heavyComputation(event.data.data);
self.postMessage({ result });
};
Use Browser Caching
Missing cache headers force repeat visitors to re-download unchanged resources. Serve static assets with long cache lifetimes and use content-hash filenames for cache busting:
nginx.conf. cache static assets aggressively
location ~* \.(js|css|png|jpg|webp|avif|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
When you update a file, change the filename hash (e.g., app.a3f9c1.js to app.b7d2e4.js). Build tools like Vite and webpack do this automatically.
Reduce Render-Blocking Resources
CSS in <head> blocks rendering. Inline critical CSS and load the rest asynchronously:
<head>
<!-- Critical CSS inlined -->
<style>
/* Above-the-fold styles only */
body { margin: 0; font-family: system-ui; }
.hero { background: #f0f0f0; padding: 2rem; }
</style>
<!-- Non-critical CSS loaded asynchronously -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
</head>
Tools like Critical and Penthouse can extract critical CSS automatically from your pages.
Improving Accessibility Score
Accessibility often has easy fixes that significantly boost your score. Many accessibility issues also make sites easier to use for everyone, not just users with disabilities.
Add Proper ARIA Labels
<!-- Missing labels hurt accessibility score -->
<button><img src="menu.svg" alt=""></button>
<!-- Proper implementation -->
<button aria-label="Open navigation menu">
<img src="menu.svg" alt="" aria-hidden="true">
</button>
Form inputs without labels are one of the most common accessibility failures:
<!-- Bad. no label association -->
<input type="email" placeholder="Enter your email">
<!-- Good. explicit label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">
<!-- Alternative. aria-label for inline contexts -->
<input type="search" aria-label="Search the site" placeholder="Search...">
Ensure Color Contrast
Minimum contrast ratios must meet WCAG 2.1 standards:
/* Good contrast - passes WCAG AA (4.5:1 ratio for normal text) */
body {
color: #333333; /* contrast ratio: 12.6:1 against white */
background-color: #ffffff;
}
/* Passes AA for large text only (3:1 ratio) */
.large-heading {
color: #767676; /* contrast ratio: 4.5:1. border of AA */
background-color: #ffffff;
font-size: 1.5rem;
}
/* Bad contrast - fails accessibility */
body {
color: #999999; /* contrast ratio: 2.8:1. fails AA */
background-color: #ffffff;
}
Use Chrome’s DevTools color picker to check contrast ratios in real-time. The DevTools Accessibility panel also lists all contrast failures across the page. WCAG AAA requires a 7:1 ratio. worth targeting for body text if your brand colors allow it.
Common color pairs that fail and their fixes:
| Failing Pair | Contrast Ratio | Fixed Pair | Contrast Ratio |
|---|---|---|---|
| #aaaaaa on #fff | 2.3:1 | #767676 on #fff | 4.5:1 |
| #ff6600 on #fff | 3.0:1 | #c45000 on #fff | 4.6:1 |
| #4a90d9 on #fff | 3.1:1 | #1d6fa8 on #fff | 4.8:1 |
Include Document Structure
<!-- Proper heading hierarchy -->
<header>
<h1>Site Title</h1>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>Article Title</h2>
<p>Content...</p>
</article>
</main>
<footer>
<p>© 2026</p>
</footer>
Never skip heading levels (going from h1 to h3) as this confuses screen reader users. Use landmark elements (<main>, <nav>, <header>, <footer>, <aside>) so screen reader users can navigate directly to sections without reading every element.
For interactive elements, ensure keyboard focus is always visible:
/* Never remove focus outlines without replacing them */
:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* Modern approach. only show for keyboard navigation */
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
Keyboard Navigation and Skip Links
Add a skip link as the first interactive element on the page so keyboard users can bypass navigation:
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header>...</header>
<main id="main-content" tabindex="-1">...</main>
</body>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #005fcc;
color: white;
padding: 8px 16px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Improving Best Practices Score
Best practices catches security and technical issues:
Use HTTPS
Never serve mixed content:
<!-- Bad - HTTP resource on HTTPS page -->
<script src="http://analytics.example.com/tracker.js">
<!-- Good - HTTPS or protocol-relative -->
<script src="https://analytics.example.com/tracker.js">
Add HTTP security headers to improve your best practices score further. Lighthouse checks for specific response headers:
nginx.conf. security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{NONCE}'; style-src 'self' 'nonce-{NONCE}';" always;
A Content Security Policy (CSP) is the most impactful header for the Best Practices score. Start with a report-only policy to identify issues before enforcing:
<meta http-equiv="Content-Security-Policy-Report-Only"
content="default-src 'self'; report-uri /csp-report">
Set Proper Doctype
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Page Title</title>
</head>
The lang attribute on <html> matters for both accessibility and best practices. Use the correct BCP 47 language tag for your content. For regional variants: lang="en-US", lang="pt-BR", lang="zh-Hans".
Avoid Console Errors
Register service worker errors with proper error handling:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration);
})
.catch(error => {
console.error('SW registration failed:', error);
});
});
}
Lighthouse flags JavaScript errors in the console as best practices failures. A common source is accessing properties on null DOM elements. Guard against this:
// Fragile. crashes if element doesn't exist
document.querySelector('.modal').addEventListener('click', handleClick);
// Robust. safe even when element is absent
const modal = document.querySelector('.modal');
if (modal) {
modal.addEventListener('click', handleClick);
}
Avoid Deprecated APIs
Lighthouse flags use of deprecated browser APIs. Common ones to watch for:
| Deprecated API | Modern Replacement |
|---|---|
document.write() |
DOM manipulation methods |
XMLHttpRequest |
fetch() |
KeyboardEvent.keyCode |
KeyboardEvent.key |
-webkit- prefixed CSS |
Standard CSS properties |
| Synchronous XHR | Async fetch with await |
Improving SEO Score
SEO is straightforward to fix once you know what Lighthouse checks. The audit covers technical SEO factors, not content quality.
Add Meta Tags
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Your unique description under 160 characters.">
<title>Primary Keyword | Brand Name</title>
<meta name="robots" content="index, follow">
<!-- Open Graph for social sharing -->
<meta property="og:title" content="Your Page Title">
<meta property="og:description" content="Your description">
<meta property="og:image" content="https://example.com/image.jpg">
</head>
The meta description should be unique per page and between 120–160 characters. Duplicate descriptions across pages are flagged by Google Search Console even if Lighthouse does not directly penalize them.
Ensure Crawlability
robots.txt
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Pages with <meta name="robots" content="noindex"> will fail the Lighthouse SEO audit if audited. Make sure staging or development pages cannot leak this tag to production.
Link text quality also matters. Lighthouse checks whether links have descriptive text:
<!-- Bad. non-descriptive link text -->
<a href="/guide">Click here</a>
<!-- Good. descriptive link text -->
<a href="/guide">Read the complete optimization guide</a>
Add Structured Data
**Know your costs →** Use our [Claude Code Cost Calculator](/calculator/) to estimate your monthly spend.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Your Article Title",
"image": ["https://example.com/image.jpg"],
"datePublished": "2026-03-15T08:00:00+08:00",
"author": {
"@type": "Person",
"name": "Your Name"
}
}
</script>
Structured data does not directly improve your Lighthouse SEO score, but it enables rich results in Google Search. For e-commerce sites, add Product and Review schemas. For local businesses, use LocalBusiness. For articles, Article or BlogPosting.
Canonical URLs
Duplicate content from trailing slashes, query parameters, or HTTP/HTTPS variants lowers crawl efficiency. Use canonical tags to declare the preferred URL:
<link rel="canonical" href="https://example.com/guide/">
Also set your preferred domain in Google Search Console (with or without www) and configure server-side redirects to enforce consistency.
Running Lighthouse Locally
Test your changes locally using Chrome DevTools:
- Open DevTools (F12 or Cmd+Option+I)
- Click the Lighthouse tab
- Select categories to audit
- Choose Mobile or Desktop
- Click “Analyze page load”
Run Lighthouse in an incognito window or a fresh Chrome profile to avoid extensions inflating or deflating your scores. Extensions that inject scripts, modify cookies, or intercept requests change what Lighthouse measures.
For more consistent results, use the Chrome CLI version:
Install globally
npm install -g lighthouse
Run against a URL and save HTML report
lighthouse https://your-site.com \
--output html \
--output-path ./lighthouse-report.html \
--only-categories performance,accessibility,best-practices,seo
Run on mobile simulation (default)
lighthouse https://your-site.com --form-factor mobile
Run desktop simulation
lighthouse https://your-site.com --form-factor desktop --screenEmulation.disabled
CLI usage gives you reproducible results you can version-control and diff over time.
For continuous monitoring, add Lighthouse to your CI pipeline using Google Chrome’s lighthouse-ci:
.github/workflows/lighthouse.yml
name: Lighthouse
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v9
with:
urls: https://your-site.com
uploadArtifacts: true
budgetPath: ./lighthouse-budget.json
Pair this with a budget file to enforce score minimums and fail the build when scores regress:
[
{
"path": "/*",
"scores": [
{ "id": "performance", "minScore": 0.9 },
{ "id": "accessibility", "minScore": 0.9 },
{ "id": "best-practices", "minScore": 0.9 },
{ "id": "seo", "minScore": 0.9 }
]
}
]
Interpreting Lighthouse Reports Effectively
Lighthouse scores fluctuate between runs because they simulate network and CPU throttling using probabilistic models. A single score does not tell you much. trends over multiple runs do. Run three to five audits and average the results before making decisions.
The Opportunities section shows potential savings in time, making it easy to prioritize. The Diagnostics section shows issues that may not have a direct time saving but affect score categories. The Passed Audits section tells you what is already working correctly and can be safely deprioritized.
When scores are inconsistent, check:
- Server response variability: Slow database queries cause TTFB spikes
- CDN cache cold starts: First request after deployment often misses cache
- Third-party scripts: External services with variable latency
- Font loading: Self-host fonts to eliminate third-party DNS lookups
Summary
Improving your Chrome Lighthouse score requires addressing multiple dimensions of web development. Start with performance optimizations that have the biggest impact: image optimization, JavaScript reduction, and Core Web Vitals improvements. Then systematically work through accessibility, best practices, and SEO fixes.
Run Lighthouse regularly during development to catch regressions early. A score above 90 across all categories indicates a well-optimized site that provides excellent user experience and ranks well in search results. Use CI budgets to prevent regressions from shipping, and interpret scores as averages across multiple runs rather than single measurements.
Related Reading
- Chrome Extension Lighthouse Audit Runner: A Developer Guide
- Chrome Extension Readability Score Checker: A Developer Guide
- Claude Code Output Quality: How to Improve Results
Built by theluckystrike. More at zovo.one