Skip to content

Why Has My Google PageSpeed Insights Score Lowered So Much?

Prod

For desktop, I have a site with a decent page speed score (currently, 96): https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fwww.usstoragecenters.com%2Fstorage-units%2Fca%2Falhambra%2F2500-w-hellman-ave&tab=desktop

Stage

I’m trying to improve the score (mostly for mobile), but I’ve somehow made it worse (currently, 69 on desktop): https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fstage.usstoragecenters.com%2Fstorage-units%2Fca%2Falhambra%2F2500-w-hellman-ave%3Fplain%3Dtrue&tab=mobile

Problem

While converting the site from Angular (the first link) to plain JavaScript (second link), I’ve managed to lower the desktop Google PageSpeed Insights score from 96 to 69.

I’ve massive reduced the amount of JavaScript and other resources (2MB on prod vs 500KB on stage).

Analysis

Looking through the numbers, the thing that stands out to me is that prod has an FCP (First Contentful Paint) of 0.7 seconds, while stage has an FCP of 2.0 seconds. This seems weird to me since stage should be much faster, but is apparently much slower.

Looking at the mobile timeline of thumbnails (desktop is a bit hard to see), it appears as if stage renders the first “full content” much faster:

Comparison of Stage and Prod Mobile Timelines in PageSpeed Insights

I highlighted the ones that visually look “complete” to me (stage is on top, prod is on bottom).

Screenshots

Here are some screenshots so you can see what I do (PageSpeed Insights differs fairly significantly each time it’s run).

Here is stage:

Stage Screenshot of PageSpeed Insights

And here is production:

Production Screenshot of PageSpeed Insights

Summary of Changes

Here are the main things I did when trying to improve the score:

  • I converted the JavaScript from Angular to plain JavaScript, significantly reducing the JavaScript required to render the page.
  • I lazy loaded JavaScript (e.g., Google Maps JavaScript isn’t loaded until it’s needed).
  • I lazy loaded images (e.g., the slideshow initially only loads the first image).
  • I reduced the number of DOM elements (from 4,600 to 1,700).
  • I am using HTTP/2 server push to load the new plain JavaScript as fast as possible.

Those changes should have improved the score.

Question

Do you have any ideas of why, despite my best efforts, the PageSpeed score tanked?

Answer

So I figured out the issue. PageSpeed Insights is drunk.

Well, it’s unreliable anyway. I was able to dramatically improve the score by simply removing the server pushing of the JavaScript files (less than 20KB of them).

This is weird because the page actually seems to take longer to display. However, Google PageSpeed Insights thinks it’s displaying sooner, and so it improves the score.

One time I tried, the mobile score went up to 99:

Mobile PageSpeed Insights Score of 99

I tried again and got a more reasonable 82:

Mobile PageSpeed Insights Score of 82

And on desktop, the score went up to 98:

Desktop PageSpeed Insights Score of 98

The interesting thing about the mobile screenshot showing 99 is that you can see in the timeline thumbnails that the image for the slideshow at the top of the page hasn’t loaded yet. So it seems like a case of Google PSI prematurely deciding that the page has “finished loading”, even though it hasn’t finished.

It’s almost like if you delay certain things long enough, Google will ignore them. In other words, the slower the page is, the better the score they will give you. Which is of course backwards.

In any event, this might be one of those things where I’ll go with the slightly less optimal approach in order to achieve a higher score. There may also be a middle ground I can explore (e.g., have the first JavaScript file inject link rel=preload tags in order to load the rest of the JavaScript files immediately rather than wait for the full chain of modules to resolve).

If somebody can come up with a more satisfactory explanation, I’ll mark that as the answer. Otherwise, I may end up marking this one as the answer.

Middle Ground Approach

EDIT: Here’s the middle ground approach I went with that seems to be working. First, I load a JavaScript file called preload.js that is included like this:

<script src="/preload.js" defer></script>

This is the content of the preload.js file:

// Optimization to preload all the JavaScript modules. We don't want to server push or preload them
// too soon, as that negatively impacts the Google PageSpeed Insights score (which is odd, but true).
// Instead, we start to load them once this file loads.
let paths = window.preloadJavaScriptPaths || [],
    body = document.querySelector('body'),
    element;
paths.forEach(path => {
    element = document.createElement('link');
    element.setAttribute('rel', 'preload');
    element.setAttribute('as', 'script');
    element.setAttribute('crossorigin', 'anonymous');
    element.setAttribute('href', path);
    body.appendChild(element);
});

The backend creates a variable on the window object called preloadJavaScriptPaths. It is just an array of strings (each string being a path to a JavaScript file, such as /app.js).

The page still loads pretty fast and the score is PSI score is still good (80 mobile, 97 desktop):

enter image description here