Skip to main content
Case Study 7 min read

From 81 to 100 on Google PageSpeed: The CSS Rule That Was Hiding My Own Site

David Orlov

David Orlov

Founder, Orlov Digital · August 4, 2026

I build fast websites for a living. So when I ran Google's PageSpeed test on my own site and got an 81 on mobile, that stung a little.

What followed was a few hours of chasing the wrong thing, one genuinely surprising discovery, and a mistake I made along the way that I am going to include here because leaving it out would make this article less useful. The site now scores 100 on both mobile and desktop. Here is the whole thing.

Mistake One: I Was Comparing Two Different Tests

My first reaction was that something had broken, because I remembered scoring 96 back in the winter.

I had. On desktop. The 81 was mobile. Those are two completely different tests with different throttling, and comparing them tells you nothing. My actual mobile score in January was 93, so the real drop was 93 to 81, not 96 to 81.

That sounds like a small correction. It is not. If I had kept believing the site fell off a cliff, I would have gone looking for something catastrophic instead of the handful of ordinary things that were actually wrong. Always compare mobile to mobile.

Mistake Two: I Assumed I Knew the Answer

I had recently added case study screenshots to the homepage. Big 1920 pixel wide images. Obviously that was the problem.

So I spent real time generating smaller versions of every screenshot and wiring up responsive images so phones would download a 26KB file instead of a 120KB one. That work was worthwhile. Real visitors on real phones now download about a quarter of what they used to.

It moved the score by exactly zero points.

Because the images were below the fold and lazy loaded, they were never the problem. I had optimized something real and irrelevant at the same time, which is the most expensive kind of work you can do.

What the Measurement Actually Said

When I stopped guessing and read the report properly, the interesting number was the breakdown of Largest Contentful Paint, which is the metric for how long it takes the biggest piece of content to appear:

  • Time to first byte: 40ms. The server was fine.
  • Element render delay: 910ms. Everything had arrived and nothing was being drawn.

Two other details mattered. The element being measured was a plain text link, not an image. And Cumulative Layout Shift was 0 with Total Blocking Time at 20ms, meaning nothing was jumping around and my JavaScript was not the problem either.

So: the server was quick, the content had downloaded, the layout was stable, the scripts were light, and the page still sat there for nearly a full second doing nothing. That combination only really has one explanation. Something was telling the browser not to draw yet.

The Culprit

It was this, sitting in my stylesheet where I had put it months earlier:

body { opacity: 0; animation: fadeIn 0.5s ease 0.1s forwards; }

A soft fade in for the whole page. It looks nice. I liked it.

Here is the part I did not know: a browser does not consider content painted while its opacity is zero. As far as the measurement is concerned, an invisible page is a page that has not loaded. So a 0.1 second delay plus a 0.5 second fade was a hard floor under my score, entirely self inflicted, on a page that was otherwise ready to display almost immediately.

I had written a performance problem in the form of a design decision. Nothing in my code was slow. I was just hiding it.

The Twist: There Were Two of Them

I found that rule in my header template, removed it, tested, and nothing changed. The page was still invisible for the same amount of time.

That was the genuinely useful moment. When a fix that should obviously work does nothing, it usually means your diagnosis is right but incomplete. So I searched the whole project instead of the one file I had been looking at, and there it was a second time, in a different file, doing the same thing.

One rule in the template, one in the stylesheet. Removing either one alone accomplished exactly nothing, because the other one was still there.

If I had trusted the first fix and moved on, I would have concluded the diagnosis was wrong and gone back to optimizing images.

The Fix

I did not want to lose the effect entirely, so instead of fading the whole page, the fade now runs only on the sections below the hero:

main > section:not(:first-of-type) { animation: fadeIn 0.5s ease both; }

The first thing you see paints instantly. Everything after it still fades in as you scroll, so the site feels the same to a human being. It just stopped lying to the browser about whether it had loaded.

Measured on the live site afterward, Largest Contentful Paint went from 3.8 seconds to 516 milliseconds, and the element being measured became the actual headline instead of a link further down the page.

The Other Four Things

The opacity rule was the big one, but a few others were worth doing:

Two stylesheets were blocking the render. Combined they were about 8KB compressed, which is small enough that the two round trips to fetch them cost more than the bytes did. They are now written directly into the page.

My JavaScript was being sent with no-store. Meaning every visitor downloaded it fresh, every single time, forever. My CSS was set to expire after a day. Both are now cached for a year, which is safe because their filenames change whenever I edit them.

Analytics was the heaviest thing on the page. 163KB, and it owned every single long task blocking the main thread. It does not need to run before the page appears, so it now loads once the browser is idle or the visitor interacts, whichever comes first, with a hard timeout so nobody stops being counted.

The page HTML was marked no-store by PHP's session handling, which also disables the browser's back button cache. That is now a policy that still prevents shared caching but lets the browser do its job.

The Mistake I Made Doing This

Partway through, I decided my font preloads were wrong and changed them.

They were not wrong. I had run a quick script to check which font weights appeared at the top of the page, my script had a flaw in how it selected elements, and it missed several. I confidently "fixed" something that was already correct, and my change would have made it slightly worse by preloading fonts used on small text while dropping the one used by the biggest headline on the page.

I caught it by checking the actual computed weight of each element individually instead of trusting my earlier sample, and put it back.

The lesson is not "be careful." It is more specific than that: a measurement is only as good as what it looked at. When a measurement disagrees with reading the code, do not just pick the one you like better. Find out why they disagree.

Results

  • Mobile: 81 to 100
  • Desktop: 100
  • Largest Contentful Paint: 3.8s to 516ms
  • Homepage screenshot payload: about 255KB to about 62KB
  • Render blocking stylesheets: 2 to 0

What I Would Tell You to Check First

If your own site is scoring lower than you expect, in order:

  1. Confirm you are comparing the same test. Mobile and desktop scores are not interchangeable and the gap between them is usually large.
  2. Read the LCP breakdown before touching anything. If the delay is in rendering rather than loading, no amount of image compression will help you.
  3. Look for anything that hides the page. Fade ins, splash screens, "loaded" classes added by JavaScript, opacity transitions on the body. These are extremely common and they are almost never suspected, because they were added on purpose by someone who liked how they looked.
  4. Check what your server says about caching. Mine was quietly telling browsers never to keep my JavaScript.
  5. Fix the thing the data points at, then measure again. Not the thing you assumed on the way in.

The part I keep coming back to is that the slowest thing on my website was not code. It was a design choice that looked good and cost nearly a second of every visitor's time, and it survived for months because it never occurred to me that "make it fade in" and "make it slow" were the same sentence.

If your site feels slow and you would rather someone else dig through it, get in touch. Speed is measurable, which means it is fixable, and usually the answer is stranger than you would guess.

Let's talk

Need help with your website?

No pressure, no sales pitch. Just a straight conversation about what your business actually needs.

Get in Touch