In This Article
Last Updated: June 18, 2024 |
I have written two detailed guides to improve your website’s page speed (How to Score 100 on Google PSI: A Complete Guide to Optimizing Your WordPress Site Performance and Speed and How to Score 100 on Google PSI: More Lessons Learned). Those discuss multiple online testing tools that are useful, but the core focus was on Google’s PagesPeed Insights tool.
After working to improve your page speed you’ll want to monitor it to make sure things are still performing well. Life being what it is, remembering to regularly run a manual check is not convenient or likely. Ideally, there should be a way to automate the process. Recently, I began to investigate the options and found three options worth considering.
Google API Key
For the potential solutions discussed below you will need an API key. This is something Google provides for free but you will need a Gmail or Google account. The layout and process involved in enabling APIs and obtaining an API Key often changes, but the general process is:
- Login to Google API Console.
- Create a new project. Name the project anything you’d like, such as: PageSpeed Insights. Agree to the terms and service if asked. It may take 10-20 seconds for the new project to be created, be patient. If you have already created a project previously that you want to use, select that project.
- Enable the PageSpeed Insights API. After your project has been created, you can enable the google services you want to use from the API Library. Find / Search for “PageSpeed Insights API”, and click it. Enable the PageSpeed Insights API.
- Create credentials for using the PageSpeed API from the credentials page. The type of credentials that we want to make is “API Key.” Google will generate an API Key for you to use. Copy it to your clipboard to use it later. You can come back to the credentials page if you misplace it. (Note: You will be given the option to restrict usage of your API key. Be careful that you do not accidentally add a restriction that will block your site form using this key.)
WordPress Google PageSpeed Insights Plugin
The Google Pagespeed Insights WordPress plugin is a pretty impressive tool. It will query PageSpeed Insights for all the pages of your site (beware if you have many). You can sort your page reports by their score to prioritize the largest areas of opportunity on your site. Click on any page to see a detailed report of the results, with recommendations based upon current industry best practices.

In the options section you can configure the plugin to run reports for desktop, mobile or both. Since the results vary a lot between the two I recommend using both.
One nice feature is the ability to schedule the plugin to run. Unfortunately, I am unable to actually access the scheduling section of the options page (or the advanced options section). I have posted in the support section asking about this so hopefully the plugin author will address it. It may just be an issue specific to my setup, but until resolved it won’t allow for ongoing tracking.
Another concern I have with the plugin is that it seems to automatically check every page on the site. Once you have those results you can click on any page result and choose Ignore to prevent it from running. But, it would be nice to specify which pages you want to run in the main options page. That may actually be an advanced setting, but I have no idea since I cannot access that section.
Google Sheet Monitor for PageSpeed Insights
The more I use Google Sheets the more impressed I become. I have created several public projects, including:
- Travel Expenses Tracking Template
- Packing List Generator
- Appalachian Trail Planning, Resupply & POI
- Thru-Hike Tracking Info
Recently, I came across projects by Robert Ellison and Rick Viscomi that can track PageSpeed Insights. These spreadsheets use the script editor and pivot tables. I haven’t previously used either, so it was a good chance to learn some new things.
Neither of those spreadsheets quite do what I want so I decided to create my own. My spreadsheet uses the best of both and adds new pieces as well. One of the new features is the ability to email yourself the results. I have made a public version that you can copy.
PageSpeed Insights Monitor: Lighthouse and Fast FCP from CrUX – Public
(NOTE: please don’t ask for editing permission, just use the “Make a copy” option in the “File” menu)
This spreadsheet will track various PageSpeed Lighthouse and CrUX results. You can track one or more websites (useful if you want to compare your site to competitors). If you schedule it to run daily (recommended) it will create charts to track desktop and mobile performance over time.
Modifying the Spreadsheet
After making a copy of the sheet, you will need to modify it a bit to suit your needs. First, use the “Extensions” menu to select the “Apps Script” option.
Note: The script editor was previously accessed via the “Tools” menu but it seems Google has updated their user interface. I have updated some of the screenshots but not all so if you notice any discrepancies between a screenshot here and what you are seeing, that’s the explanation. |

Next, edit the pageSpeedApiKey
and pageSpeedMonitorUrls
variables to use your API key and the URLs that you would like to monitor. Note that PageSpeed Insights now lets you test not only a single page of your site but also the aggregated page speed data across all pages of your site. To test that simply use the syntax origin:https://www.domain.com
.

Now edit the email address where you want to send the results of each run.

Running the Script
Finally, save the changes (via the disk icon or Ctrl+s) and run the script (the Run menu option) to verify everything is working properly. The monitor
function should be selected by default but if not, select it first.
Note that to run the script, you will first need to authorize it. A window will pop up warning you that authorization is required. Just choose the “Review Permissions” button.

Next you will be asked to choose your account.

Now you will be shown a warning that the script isn’t verified. You will need to override this warning by clicking the “Advanced” link at the bottom.

Now just click the “Go to PSI CrUX Monitor (unsafe)” link.

If the script runs with no errors, view the spreadsheet “Results” sheet. You should see a row entered for each URL you are tracking. Note that one error that might appear while running the script looks like:
1 | Lighthouse: FAILED_DOCUMENT_REQUEST. Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests. net::ERR_TIMED_OUT. |
This happens if the server hosting a site you are trying to test is configured to block scripts like PageSpeed Insights. If it’s your own URL you will need to investigate with your hosting provider. If it is a competitor URL you may be out of luck. FWIW, I was unable to run the script using either Google or Yahoo when I was trying to test things.
Scheduling Daily Runs
If everything is working well, you will want to set a schedule to have the script run itself automatically. To do so, you need to set a “trigger” to run the monitor
function daily, which will append a new row for each URL every day. To set that up, go to the “Triggers” menu on the left sidebar (a ringing alarm clock icon) and then click the “Add Trigger” button.

Add a new trigger with the following configuration:
- Run the
monitor
function - Choose the Head deployment
- Choose “Time-driven” event source
- Choose “Day timer” for the type of time based trigger
- Select any hour for the script to run, or leave it on the default “Midnight to 1am”


After saving the trigger, return to this sheet daily to see the performance stats for all monitored URLs or origins.
Pivot Tables
I am pretty new to pivot tables myself so I am in no position to offer detailed advice or help. Still, one thing that may happen is that your pivot table data won’t populate automatically. This is because it was looking for the URLs that you changed. The way to fix this is to change the “Filters” section on the “Pivot Table Editor” right sidebar. On the “URL” box use the “Status” dropdown box to select all your URLs. Once you do this, the data from the “Result” sheet should populate the rows.

Once you have some days of data the charts should graph the values for each URL in a nice way. The screenshot below is from Rick Viscomi’s version of the script since my own hasn’t yet run for enough days.

Finally, make sure you receive an email after successfully running the script. That should provide convenient push notifications of the results and remind you to check the sheet occasionally.
PHP PageSpeed Insights Monitoring Script
I think the Google Sheets solution I just described is the best option and the WordPress plugin has a lot of promise too, but before I knew about either of those I used a PHP script run with a cron job to monitor my sites. That script was lightly modified from one written by Adam Malone and served me well for the past few years.
That script was written for the first version of Google’s API, which is now on the fifth version. Older versions are ending, which is what prompted me to research a replacement and how I found the other options. I didn’t find any similar script already written for the latest version so I decided to write my own.
The current version of PageSpeed Insights barely resembles the one used a few years ago, for better and worse. Previously, Google spelled out where your page was doing well and where it was falling short, providing useful tips for each. Those tips still sort of exist but in a different, more confusing format. It’s also not clear how those tips tie directly to the score you see on the results page.
I still haven’t mastered all the results that the API returns but I decided to use the ones that the PSI page highlights. Note that many of these are not listed in the results of the Google Sheets tracking as they aren’t numeric. They are, however, given in the reports generated by the WordPress plugin.
All of which is a long-winded way of saying I am not sure how helpful this script will be. It may need some small to big modifications in the future. But, for now, if you are interested, here is the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | <?php ini_set('max_execution_time', 300); // ================================================================ // === A PHP script to look up and parse Google Pagespeed results // ================================================================ function mailit ($message,$format) { if ( isset($_GET['to']) ) $to = $_GET['to']; $subject = 'PageSpeed Test Results - HTML'; // To send HTML mail, the Content-type header must be set $headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: text/html; charset=UTF-8\r\n"; $headers .= "X-Mailer: PHP/" . phpversion() . "\r\n"; if ($format=="HTML") { $send_message = mail ( $to, $subject, $message, $headers ); } else { $send_message = mail ( $to, $subject, $message ); } if ( $send_message ) { // --- the message was sent... echo 'Email was sent. Check your email inbox.'; } else { // --- the message was not sent... echo 'Email was not sent!<br />'; echo "<strong>Last PHP error</strong>: "; print_r(error_get_last()); echo "<br/>"; } } function printr($data) { echo sprintf('<pre>%s</pre>',print_r($data,true)); } function replace_placeholders($format, $args) { $i = 1; foreach ($args as $arg) { $format = str_replace("\$" . $i, "$arg->value", $format); $i++; } return $format; } // ================================================================ // === Key variables // ================================================================ // --- URLs you want to track (could be multiple sites or multiple pages on a single site) // --- If you add too many pages the script may timeout so beware of that $urls = array('https://www.domain1.com', 'https://www.domain2.com'); // --- Your API Key - you need one to run this script // --- View https://developers.google.com/speed/docs/insights/v1/getting_started#before_starting to get a key // --- To test via browser use the following URL: // --- https://www.googleapis.com/pagespeedonline/v1/runPagespeed?url=https://www.domain.com&key=YOUR_API_KEY $key = 'YOUR_API_KEY'; // --- PageSpeed Insights Lighthouse results to include // --- Commment out any you don't want to include and add any I may have missed $audit_ids = array(); $audit_ids[] = "time-to-first-byte"; $audit_ids[] = "uses-optimized-images"; $audit_ids[] = "critical-request-chains"; $audit_ids[] = "uses-webp-images"; $audit_ids[] = "estimated-input-latency"; $audit_ids[] = "font-display"; $audit_ids[] = "resource-summary"; $audit_ids[] = "uses-responsive-images"; $audit_ids[] = "offscreen-images"; $audit_ids[] = "third-party-summary"; $audit_ids[] = "render-blocking-resources"; $audit_ids[] = "image-aspect-ratio"; $audit_ids[] = "unused-css-rules"; $audit_ids[] = "tap-targets"; $audit_ids[] = "unminified-css"; $audit_ids[] = "unminified-javascript"; $audit_ids[] = "content-width"; $audit_ids[] = "uses-long-cache-ttl"; //$audit_ids[] = "color-contrast"; // --- this can produce a LOT of result text so I am excluding it but you may wish to include it $audit_ids[] = "robots-txt"; $audit_ids[] = "total-byte-weight"; $audit_ids[] = "total-blocking-time"; $audit_ids[] = "uses-text-compression"; $output = ""; foreach ($urls as $url) { $strategy = ""; $totest = 1; // Set to 1 for desktop, 2 for both desktop and mobile for ($i=0; $i<$totest; $i++) { $strategy = ($i==0) ? "desktop" : "mobile"; $data = json_decode(file_get_contents("https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=$url&key=$key&strategy=$strategy&category=accessibility&category=performance&category=pwa&category=best-practices&category=seo"),true); $output .= "<strong>Page tested</strong>: " . $url . " (" . $strategy . ")<br />"; $performance = $data['lighthouseResult']['categories']['performance']['score'] * 100; $output .= "<strong>Overall Score</strong>: " . $performance . "<br />"; $accessibility = $data['lighthouseResult']['categories']['accessibility']['score'] * 100; $output .= "<strong>Accessibility Score</strong>: " . $accessibility . "<br />"; $seo = $data['lighthouseResult']['categories']['seo']['score'] * 100; $output .= "<strong>SEO Score</strong>: " . $seo . "<br />"; $bestpractices = $data['lighthouseResult']['categories']['best-practices']['score'] * 100; $output .= "<strong>Best Practices Score</strong>: " . $bestpractices . "<br />"; $pwa = $data['lighthouseResult']['categories']['pwa']['score'] * 100; $output .= "<strong>PWA Score</strong>: " . $pwa . "<br />"; $output .= "<br />"; // --- Uncomment the following lines if you want to see the full results for each category // --- of test to help decide if there are any others you wish to include that I don't already /* printr($data); echo "<hr>"; printr($performance); echo "<br>"; printr($accessibility); echo "<br>"; printr($seo); echo "<br>"; printr($bestpractices); echo "<br>"; printr($pwa); echo "<br>"; */ // --- I have commented out the descriptions for each result included but if you prefer to see them, just uncomment each $output .= "<strong>Time to First Byte (TTFB)</strong>: " . $data['lighthouseResult']['audits']['time-to-first-byte']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['time-to-first-byte']['description'] . "<br />"; $output .= "<strong>Time to Interactive</strong>: " . $data['lighthouseResult']['audits']['interactive']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['interactive']['description'] . "<br />"; $output .= "<strong>Speed Index</strong>: " . $data['lighthouseResult']['audits']['speed-index']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['speed-index']['description'] . "<br />"; $output .= "<strong>First Contentful Paint Score</strong>: " . $data['lighthouseResult']['audits']['first-contentful-paint']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['first-contentful-paint']['description'] . "<br />"; $output .= "<strong>First CPU Idle</strong>: " . $data['lighthouseResult']['audits']['first-cpu-idle']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['first-cpu-idle']['description'] . "<br />"; $output .= "<strong>First Meaningful Paint</strong>: " . $data['lighthouseResult']['audits']['first-meaningful-paint']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['first-meaningful-paint']['description'] . "<br />"; $output .= "<strong>Input Latency</strong>: " . $data['lighthouseResult']['audits']['estimated-input-latency']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['estimated-input-latency']['description'] . "<br />"; $output .= "<strong>Max Potential FID</strong>: " . $data['lighthouseResult']['audits']['max-potential-fid']['displayValue'] . "<br />"; //$output .= $data['lighthouseResult']['audits']['max-potential-fid']['description'] . "<br />"; $output .= "<br />"; $output .= "<strong>Items to investigate</strong><br />"; $output .= "<ul>"; $audits = $data['lighthouseResult']['audits']; foreach($audits as $audit) { $id = $audit['id']; $name = htmlentities($audit['title']); $description = $audit['description']; $description = htmlentities(substr($description,0,strpos($description,"[Learn"))); $score = $audit['score']; if (isset($score) && !is_null($score) && $score != "1") { $output .= "<li>"; $output .= $name . "<br />"; if (!is_null($description) && $description!="") $output .= $description . "<br />"; if (in_array($id,$audit_ids)) { if (isset($audit['details']['items'])) { $output .= sprintf('<pre>%s</pre>',print_r($audit['details']['items'],true)); } } $output .= "<br /></li>"; } }; $output .= "</ul>"; $output .= "<hr>"; } } // --- Output and email the results echo $output; $output_text = str_replace("<strong>", "=== ", $output); $output_text = str_replace("</strong>", "", $output_text); $output_text = str_replace("<em>", " --- ", $output_text); $output_text = str_replace("</em>", "", $output_text); $output_text = str_replace("<br />", "\n", $output_text); $output_text = str_replace("<hr>", "\n", $output_text); //echo $output_text; // --- Mail the results. If you prefer use a text-only format, uncomment the next line and comment the following one. //mailit($output_text,"TEXT"); mailit($output,"HTML"); ?> |
Conclusion
I hope one of the three ways to automate your PageSpeed Insights checks will prove useful. I am writing this post before really trying them out so if you notice any problems please leave a comment and I will investigate.
Click to See or Add Your Own »
Hi Jeff,
This is a great article. I have tried the Google sheet, and it is a great way to track these insights over time!
Unfortunately the script has stopped working for me.
I’m getting the following error message:
TypeError: Cannot read property ‘numericValue’ of undefined
Would you know how to fix this?
I noticed that also but was too busy to look into it until just now. After some troubleshooting it seems that the
estimated-input-latency
andfirst-cpu-idle
measurements are no longer supported/available via the Google API. I am not sure how that happened since it’s not a new version of the API (AFAIK) so if anyone reading this knows, feel free to share. Anyway, the easiest way to fix things is to simply comment out or delete all the lines referencing those two strings (should be 2 groups of 6 lines so 12 lines total). If you view the public version I have commented them out and added a commented line above explaining things so you can check that if in doubt.Thank you so much for this! Out of all the solutions I’ve run across, this one does exactly what I’m looking for!
I’m glad to hear that.
The template and scripts works really well except :
1. Column mismatched. Your sample data, our real data, and column name are not aligned. Please suggest.
2. Google script 6-minute execution time limit. This limit our URL list to only 2-3 URLs per execution. I think there is nothing we can do about this ?
Thanks for your comment. I have also notice the time limit and have wondered if there is a way to extend that but have been too lazy to investigate. If anyone knows, please chime in.
As for the column mismatch, can you be more specific. I am not sure what you are referring to and cannot see what you see.
I forgot to attach the screenshot.
https://i.imgur.com/PuKrE3F.png
Thanks for that. Unfortunately, I don’t really have any idea how to help you because my copy of the script for my personal sites is working just fine and all the columns match the data output. Did you make any modifications other than just the list of websites to run?
Even your original template has blank columns though.
https://i.imgur.com/D7LC309.png
OK, now that I take a look at the first screenshot you sent I see your situation and mine are actually the same. My live copy is also not showing data for the last six columns (the other two that are missing for the sample data may be because I took that screenshot before I added those extra fields, but it was so long ago that I wrote the post that I can’t be sure). Looking at the history of my own sheet, I see the last six columns that are now not working were working until June 1. So, I am guessing something changed with Google’s API. Not sure if those tests are still available but the reference calls have changed or if they are no longer available. I will try to investigate but don’t have time now so if you (or anyone else) learns something helpful, please share.