Hitting 100 across all four Lighthouse categories — Performance, Accessibility, Best Practices, SEO — sounds impossible. Or worse, sounds like a thing that requires hacks: ridiculous tricks to game the score that make the actual user experience worse.
It isn't either of those. Lighthouse 100 across the board is a stack-and-discipline problem. Pick the right stack, hold the discipline, and the scores follow without trickery. The four 100s aren't even particularly impressive once you've seen them happen; they're a side effect of doing the basics consistently.
This post is the playbook. The stack we use, the discipline that keeps the scores there, and the case where we hit four 100s on a real project.
What "100" actually means per category
Each Lighthouse category measures different things. Worth being precise:
Performance (100):
- Largest Contentful Paint (LCP) under 1.2s on the synthetic mid-tier mobile profile.
- Total Blocking Time (TBT) under 200ms.
- Cumulative Layout Shift (CLS) under 0.1.
- Speed Index under 3.4s.
- First Contentful Paint (FCP) under 1.8s.
In practice, hit all five and the score is 100. Miss any one and it drops to 95-99.
Accessibility (100):
- All automated checks pass: alt text, ARIA correctness, contrast, form labels, heading hierarchy, language attribute, link text.
- About 50 specific checks. The audit is binary per check; 100 means all pass.
Lighthouse only catches the automated subset (~30% of real accessibility issues). Hitting 100 here is necessary but not sufficient for genuine accessibility.
Best Practices (100):
- HTTPS everywhere, no mixed content.
- No deprecated APIs in use.
- No console errors.
- No security vulnerabilities in detected dependencies.
- Proper Content Security Policy headers (recent addition).
- Images use modern formats and reasonable aspect ratios.
SEO (100):
- Title and meta description present and reasonable length.
- Page is crawlable (robots.txt allows, no
<meta robots noindex>). - Links have descriptive text.
- Page has a viewport meta tag.
- Document has a
<title>and<html lang="...">.
The simplest of the four. Most templates get this for free.
The stack that makes 100 achievable
Specific technology and configuration choices that compound to make the four 100s realistic:
Framework: Next.js App Router (server-first)
Server components render HTML on the server. The browser receives finished HTML, not a JavaScript blob that has to hydrate. This is the biggest single factor for Performance scores.
A heavy SPA framework (React Router in client mode, default Vite + React setup) ships hundreds of KB of JavaScript before anything renders. Server components ship the JS only for the interactive parts, which on a marketing site is a small fraction of the page.
Hosting: edge or CDN-fronted
Static HTML served from the edge has TTFB under 100ms anywhere in the world. Dynamic HTML from a single-region origin has TTFB of 200-400ms outside that region. The Performance score is sensitive to TTFB; serving from the edge is a free 5-10 score points on average.
We default to Vercel for the simplicity. Cloudflare Workers + Pages is another excellent choice. Self-hosting with Cloudflare in front of an origin server also works.
Images: format and sizing discipline
Every image:
- Served as WebP or AVIF, not JPEG/PNG (the modern formats are 25-50% smaller for the same quality).
- Has
widthandheightattributes (prevents CLS). - Has
srcsetwith multiple sizes (browsers download the right size, not always the biggest). - Has
priorityon the hero (preloads it instead of waiting for layout). - Has
loading="lazy"on everything else (defers off-screen images).
This is the discipline that's annoying to enforce manually. Use Next.js's <Image> component (or an equivalent) and it's handled. Bypass the component and it's not.
Fonts: subset and preload
- Self-host fonts (don't load from Google Fonts at runtime; let your build pull them in).
- Subset to the characters you actually use (English + your specific locale).
- Use
font-display: swap(better LCP) oroptional(best LCP, worst font fidelity). - Preload the critical font in the head:
<link rel="preload" as="font" href="..." crossorigin>.
These four together make the difference between fonts that block rendering and fonts that don't.
JavaScript: minimal and code-split
- Use server components by default; client components only where interactivity is required.
- Audit the bundle. Anything bigger than 100KB gzipped on first load should have a justification.
- Dynamic-import non-critical components (modals, dropdowns that aren't open yet).
- No polyfills for browsers you don't support.
A marketing site should ship 30-100KB of JavaScript on first load. If yours ships more, you have an opportunity.
CSS: Tailwind + tree-shake
Tailwind ships only the classes you actually use. The final CSS file for a typical marketing site is 5-15KB gzipped, even with hundreds of pages. Tree-shaking works automatically.
The alternative (component libraries that ship 200KB of CSS) makes Lighthouse 100 hard or impossible without aggressive purging.
Third-party scripts: ruthless filtering
Analytics, chat widgets, A/B testing tools, support widgets. Each one is "just a small script." Cumulatively they cost 50-100 points of Performance score.
Discipline:
- Privacy-respecting analytics (Plausible, Umami) instead of GA4 if you can. Smaller scripts, no consent banner.
- Load chat widgets lazily — not on first paint, but on user intent.
- A/B testing tools either at the edge (no client-side script) or skipped entirely.
- Marketing automation pixels: deferred or removed if you can.
For most marketing sites we ship: one analytics script (Plausible, ~1KB), no chat widget, no marketing pixels. The site loads fast because there's nothing slowing it down.
The discipline that keeps the scores there
A Lighthouse 100 at launch is achievable. Keeping it for a year is the harder problem. Three habits:
1. Lighthouse on every PR
Run Lighthouse CI against staging on every pull request. Fail the build if any category drops below a threshold (we use 95 as the floor; below that, the PR needs justification).
This catches the slow drift. A new image without priority. A new third-party script. A regression in CSS that broke font-display. Without CI checks, these accumulate.
2. Bundle budget enforcement
bundlesize or size-limit in the test suite. The first-load JavaScript has a budget (e.g., 100KB gzipped). Any PR that exceeds the budget fails.
When the budget is hit, the team is forced to either justify the increase or trim something else. Without the budget, bundle size only grows.
3. Quarterly review of third-party scripts
Every quarter, someone reviews the list of third-party scripts loading on the site. Question every one: "Why is this still here? When did we last use it?"
Marketing teams add scripts. Marketing teams rarely remove them. The quarterly review is the only mechanism that prevents the gradual decay of every score.
A real project
We hit Lighthouse 100 across all four categories on a B2B SaaS marketing site we shipped recently. The numbers, as measured in the lab:
- Performance: 100 (LCP 0.9s, CLS 0.02, FCP 0.8s, Speed Index 1.4s)
- Accessibility: 100 (all automated checks)
- Best Practices: 100
- SEO: 100
The stack: Next.js App Router, Vercel, Tailwind, Plausible analytics. No chat widget. No marketing automation. The custom Sanity-backed CMS with embedded Studio.
The build effort wasn't significantly more than a "good but not 100" site. The difference was discipline at the margins: every image went through <Image>, every interactive element was client-component only when needed, every third-party script got justified.
A month after launch, with content edits and a couple of new features, the scores were Performance 98, Accessibility 100, Best Practices 100, SEO 100. The 98 was caused by a single PR that added a background image without srcset. The Lighthouse CI flagged it on the next PR; we fixed it; back to 100.
Six months in, the scores are still 99-100 across the board. The CI is doing its job.
Where four 100s is overkill
Honest cases where chasing this is wasted effort:
An internal tool. Performance still matters (users are the team). Accessibility matters legally and morally. But you don't need 100; 80-90 is plenty for an internal CRUD app.
An app where the marketing page is one of fifty pages. The marketing page should hit 100. The app pages (behind login) have different constraints; aiming for 100 there is a different project.
A content site with heavy user-uploaded media. If you can't control the image quality (users upload what they upload), you can't hit Performance 100 across the board. Focus on hitting it on the pages you control.
A site that's about to be rebuilt. Don't spend three weeks optimising a site you're replacing in six. Time travels poorly.
The summary
- Lighthouse 100 across all four categories is a stack-and-discipline problem.
- Server components, edge hosting, image discipline, minimal JS, ruthless third-party filtering.
- CI enforcement keeps the scores there post-launch.
- Quarterly review prevents the gradual decay.
- It's overkill for some cases, the default for others.
The four 100s are achievable on any marketing site built with the right stack. They're a side effect of doing the basics consistently, not a hack.
If you want a marketing site built for the four 100s (and maintained at them), see how we work on redesign and support.