Bỏ qua để vào nội dung chính
How to Achieve a Perfect 100 Lighthouse Score (And Keep It)

How to Achieve a Perfect 100 Lighthouse Score (And Keep It)

Bởi Benjamin Hayes
10 thg 3, 20267 phút đọc

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 meaningful alt attributes
  • 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: none without 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.

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan