
How to Achieve a Perfect 100 Lighthouse Score (And Keep It)
A perfect Lighthouse score is achievable with the right architecture. Learn the specific techniques that move each metric from yellow to green — and how to prevent regressions.
Understanding What Lighthouse Actually Measures
Lighthouse scores four categories: Performance, Accessibility, Best Practices, and SEO. Each category is scored 0-100. A "perfect 100" usually means hitting 100 in all four. Performance is the hardest because it's a composite of real-time metrics (FCP, LCP, TBT, CLS, SI).
Important caveat: Lighthouse is a lab tool. Real user performance (RUM) is what matters for business outcomes. But a high score means you've done the right things.
Performance: The Core Web Vitals Strategy
// Measure your starting point with programmatic Lighthouse
import lighthouse from 'lighthouse';
import * as chromeLauncher from 'chrome-launcher';
async function runLighthouse(url) {
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const options = { logLevel: 'info', output: 'json', port: chrome.port };
const runnerResult = await lighthouse(url, options);
await chrome.kill();
const { categories, audits } = runnerResult.lhr;
console.log('Performance:', categories.performance.score * 100);
console.log('LCP:', audits['largest-contentful-paint'].displayValue);
console.log('TBT:', audits['total-blocking-time'].displayValue);
console.log('CLS:', audits['cumulative-layout-shift'].displayValue);
return runnerResult.lhr;
}
LCP: Get It Under 2.5s
- Preload your hero image:
<link rel="preload" as="image" href="/hero.webp"> - Use
fetchpriority="high"on the LCP image element - Avoid lazy-loading the LCP image
- Self-host fonts or use
font-display: swap - Remove render-blocking CSS from the critical path
<!-- Correct LCP image setup -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<img
src="/hero.webp"
alt="Hero"
fetchpriority="high"
loading="eager"
width="1200"
height="600"
/>
CLS: Eliminate Layout Shift
/* Always reserve space for images */
img {
width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height);
}
/* Reserve space for dynamic content */
.ad-slot {
min-height: 250px;
width: 300px;
}
/* Avoid inserting content above existing content */
.notification-banner {
position: fixed; /* doesn't shift document flow */
top: 0;
}
TBT: Break Up JavaScript
Total Blocking Time measures main-thread blocking. Each task over 50ms contributes. Target: under 200ms total TBT.
- Code-split large bundles
- Defer non-critical third-party scripts
- Move expensive computation to Web Workers
- Use
scheduler.yield()to break up long tasks
Accessibility: Common Missed Items
- All
<img>need meaningfulaltattributes - Buttons need accessible labels (not just icons)
- Form inputs need associated
<label>elements - Color contrast: 4.5:1 for normal text, 3:1 for large text
- Focus must be visible (no
outline: nonewithout alternative)
// Accessible icon button
function IconButton({ icon, label, onClick }) {
return (
<button
onClick={onClick}
aria-label={label}
className="p-2 rounded hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{icon}
</button>
);
}
SEO: The Easiest 100
- Every page needs a unique
<title>and<meta name="description"> - Proper heading hierarchy (one H1, logical H2-H6)
- All links need descriptive text (not "click here")
- Robots.txt and sitemap.xml configured
- Canonical tags on duplicate content
Preventing Regressions with CI
# .github/workflows/lighthouse.yml
- uses: treosh/lighthouse-ci-action@v11
with:
urls: |
https://your-preview-url.vercel.app
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
temporaryPublicStorage: true
A perfect 100 is a starting point, not a destination. Set up CI to catch regressions before they reach production.
Get weekly highlights
No spam, unsubscribe anytime.
Ranked.ai
AI-powered SEO & PPC service — fully managed, white hat, and built for modern search engines. Starting at $99/month.



Comments (0)
Sign in to comment
No comments yet. Be the first to comment!