DEV Community

Cover image for My typing game scored 41 on Lighthouse. Two days later it hit 100. Here's every fix.
Made Me The Dev
Made Me The Dev

Posted on

My typing game scored 41 on Lighthouse. Two days later it hit 100. Here's every fix.

I built a typing test called TypeVelocity. Pure HTML, CSS, and JavaScript — no React, no Next.js, no framework. Just files and a build script.

I finally ran Lighthouse on it.

Mobile: 41 Performance. 83 Accessibility. 92 SEO.

Before: Lighthouse mobile score of 41

Desktop was a 70 which sounds better until you realize passing is 90.

Before: Lighthouse desktop score of 70

Two days of debugging later:

After: Lighthouse mobile score of 100 across all categories

After: Lighthouse desktop score of 100 across all categories

100/100/100/100. Both mobile and desktop.

Here's what was actually wrong and what fixed it.


CLS was 1.0 (yes, the maximum)

This single issue was responsible for most of the damage.

I load CSS asynchronously using the media="print" onload trick — the browser doesn't block rendering for the stylesheet. Critical styles are inlined in a <style> tag in the HTML head so the page looks correct on first paint.

Sounds smart. Except I forgot about the stuff that's supposed to be hidden.

I had two modal overlays, a cookie banner, an adblocker banner, and a tips popup in the DOM. All of them rely on CSS to stay invisible (opacity: 0, transform: translateY(100%), etc). That CSS lives in the async stylesheet. So between HTML parse and stylesheet load, every single one of these elements was fully visible for a split second.

Lighthouse saw them flash in and disappear. Cumulative Layout Shift: 1.0.

The fix was five lines of inline CSS:

.modal-overlay { position: fixed; inset: 0;
    opacity: 0; pointer-events: none }
.cookie-banner { position: fixed; bottom: 0;
    transform: translateY(100%) }
.adblock-banner { position: fixed; top: 0;
    transform: translateY(-100%) }
Enter fullscreen mode Exit fullscreen mode

CLS dropped from 1.0 to 0.001. Performance went from 41 to 93 in one change.

If you're async-loading CSS and wondering why your CLS is terrible, check if you have any hidden elements that aren't hidden in your inline critical CSS. This was the dumbest bug I've ever shipped.

Accessibility: 83 → 100

A bunch of small things that added up:

user-scalable=no on the viewport. I had it to prevent zoom on the typing test. Turns out that's an accessibility fail — users with low vision need pinch-to-zoom. Removed it.

No <main> landmark. Screen readers use landmarks to navigate. I had a <header> and <footer> but nothing wrapping the actual page content. One tag.

Heading hierarchy. Jumped from <h1> to <h3>, skipping <h2>. Screen readers expect sequential order.

Missing aria-labels. The typing input was an invisible field overlaying the text display — no label, no role, screen readers had no idea what it was.

Contrast ratios. Cookie banner link was 2.17:1. Minimum is 4.5:1. Fixed the color.

Decorative SVGs. The logo, theme toggle icons — all missing aria-hidden="true". Screen readers were trying to announce them.

None of these were hard. I just hadn't tested with accessibility in mind.

The Google Fonts Problem

After the CLS fix, Performance was stuck at 93. FCP, LCP, and Speed Index were all around 2.6s on Lighthouse's simulated slow 4G.

I was loading JetBrains Mono and DM Sans from Google's CDN. 70KB of woff2 files, a CSS request, DNS lookups and TLS handshakes to two Google domains. Everything was async with font-display: optional — not render-blocking. But on a throttled 1.6 Mbps connection, 70KB of fonts competing for bandwidth with your actual content makes a difference.

I removed Google Fonts entirely.

--font-ui: 'DM Sans', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
Enter fullscreen mode Exit fullscreen mode

The fallback fonts were already in the stack. Without the Google Fonts <link>, the browser goes straight to system fonts. Zero external requests. The "3rd parties" section in Lighthouse disappeared completely.

Visual difference? Barely noticeable. System UI fonts are genuinely good now. Consolas vs JetBrains Mono is visible if you compare them side by side, but nobody's doing that on a typing test.

That got us from 93 to 100.

Non-composited animations (the boring one)

Lighthouse flagged 22 elements with non-composited CSS animations. Every result item in the modal had:

transition: background 0.35s ease, border-color 0.35s ease;
Enter fullscreen mode Exit fullscreen mode

Two problems:

  1. background shorthand transitions background-position sub-properties, which can't be GPU-composited. Changed to background-color everywhere.
  2. border-color can't be composited at all. These transitions were on modal elements that are hidden 99% of the time — they only fire if you toggle dark/light mode while the results modal is open. Removed them.

This probably didn't move the score directly. But cleaning it up felt right.

Other fixes

Cookie banner as LCP. The cookie consent text was being detected as the Largest Contentful Paint because it was the biggest visible text on initial render. Deferred it with a 3.5s setTimeout. Now the actual page content is the LCP.

Footer content-visibility: auto. Tells the browser to skip rendering off-screen content until the user scrolls to it. Less work during initial paint.

contain: layout style paint on modal overlays. Tells the browser it can skip layout calculations for these elements when they're hidden.

Script defer. script.min.js loads with defer. Total Blocking Time: 0ms.

What was worth how many points

Rough breakdown based on testing each change:

Fix Points gained Effort
Inline hiding CSS for modals/banners ~50 on mobile 10 minutes
Remove AdSense script ~23 (Best Practices) 1 minute
Accessibility fixes (viewport, landmarks, aria, contrast) 83 → 100 1 hour
Remove Google Fonts ~7 Performance 5 minutes
background → background-color transitions ~1-2 30 minutes
Footer/modal containment hints marginal 15 minutes

The CLS fix alone was worth more than everything else combined. If your score is low, find the big thing first. Don't start by optimizing CSS transitions when your modals are causing a full-page layout shift.

The honest part

Going from 41 to 93 took maybe three hours. Finding the CLS bug, adding inline CSS, fixing accessibility, deferring the cookie banner.

Going from 93 to 100 took the rest of two days. Auditing every CSS property in every transition. Removing custom fonts. Adding containment hints. Each optimization was worth a point, maybe two. Textbook diminishing returns.

Was it worth it? For the number, yes. For actual user experience, the jump from 41 to 93 was where all the real improvement happened. The 93-to-100 push was for my own sanity.

If you're building something and your Lighthouse score is rough — start with CLS. Check what's visible during that gap between HTML parse and CSS load. That's probably where your points went.


By the way — TypeVelocity is the typing test I built all of this for. It's free, works on your phone, no signup, no account, you just open it and type. Words mode, sentences mode, and a rhythm game mode if you want something different.

If you feel like trying it, cool. If not, that's fine too. Mostly I just wanted to share what I learned chasing that Lighthouse score because I couldn't find a single post that broke it down fix-by-fix with actual point values when I was stuck at 41.

Top comments (0)