Show off your Lighthouse scores as static HTML in Eleventy with the PageSpeed Insights API
Tags: eleventy, 11ty, lighthouse, performance
Published: 2022-03-15 21:06:00

The [Eleventy](https://www.11ty.dev) community is often focused on performance in a way that you don't tend to see with other tools, mostly because Eleventy seems to attract fans of keeping things lean and appreciating the fundamentals of the web (the holy trinity of HTML, CSS, and minimal JS – in that order), and perhaps partly due to the [Eleventy Leaderboards](https://www.11ty.dev/speedlify/) baiting everyone's competitive sides!

When I first started building my [personal site](https://wolstenhol.me) I was very focused on getting a good Lighthouse score. It's dipped then recovered a few times over the years, but it has been at the point where it's something I've wanted to show off in my site's footer for a while.

At first, I did this using Zach Leatherman's [`<speedlify-score>` web component](https://github.com/zachleat/speedlify-score) which uses the Eleventy Leaderboards/[Speedlify](https://www.speedlify.dev) as its source of data.

This worked well, but the leaderboards are only updated every so often, so it took time for improvements to show up. It also felt counter-productive to be using a third-party package with its own client-side JavaScript and CSS to report on performance metrics!

I started looking at something I could use at build time instead, and found that the [PageSpeed Insights API](https://developers.google.com/speed/docs/insights/v5/get-started) can return the results of a Lighthouse test!

----

## Getting data from the PageSpeed Insights API and using it in Eleventy

This isn't a full tutorial and assumes some prior knowledge of working with npm, APIs, and environment variables.

Let's take a look at how I got my Eleventy site to trigger a Lighthouse test each time it is built and to show the results in my site footer.

### Get an API key and store it in an environment variable

Head to the [PageSpeed Insights API's getting started guide](https://developers.google.com/speed/docs/insights/v5/get-started) and follow the links to get an API key.

Add the key into an environment variable (locally and on your remote environments - don't forget to do it on Netlify etc or your deployments will fail!) called `PAGESPEED_API_KEY`.

### Create a JavaScript data file to hold our API call

Create a `lighthouse.js` file inside your Eleventy data directory.

Make sure you have installed the `dotenv` and `@11ty/eleventy-cache-assets` packages (or alternatives, if you prefer).

Add the script below, swapping `https://example.com` for your  own URL, or an environment variable representing your own URL.

```javascript
require('dotenv').config();
const Cache = require('@11ty/eleventy-cache-assets');

module.exports = async function () {
  const params = new URLSearchParams();
  params.append('url', 'https://example.com');
  params.append('key', process.env.PAGESPEED_API_KEY);
  // We use the fields query string param to ask the Google API to only
  // return the data we need - a score and title for each category in the
  // Lighthouse test. Without this, the API returns a *lot* of data, which
  // isn't the end of the world but is also unnecessary.
  params.append('fields', 'lighthouseResult.categories.*.score,lighthouseResult.categories.*.title');
  params.append('prettyPrint', false);
  // I use the mobile strategy, but `desktop` is a valid value too.
  params.append('strategy', 'mobile');
  // I've not used the PWA category, but you could if it is relevant to your site.
  params.append('category', 'PERFORMANCE');
  params.append('category', 'ACCESSIBILITY');
  params.append('category', 'BEST-PRACTICES');
  params.append('category', 'SEO');

  let data = await Cache(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${params.toString()}`, {
    duration: '1d',
    type: 'json',
  });

  data = data.lighthouseResult.categories;

  const getGrade = function (score) {
    if (score < 0.5) {
      return 'bad';
    }
    if (score < 0.9) {
      return 'ok';
    }
    return 'good';
  };

  Object.keys(data).map(function (key) {
    data[key].score = (data[key].score * 100).toFixed();
    data[key].grade = getGrade(data[key].score);
  });

  return {
    categories: data,
  };
};
```

The function will return an object containing another object called `categories`, inside of which will be your scores. I nested the scores inside of the `categories` object in case we ever want to return anything else from `lighthouse.js`, like some Core Web Vitals scores perhaps?

#### Example output

```json
{
  "categories": {
    "performance": { "title": "Performance", "score": "100", "grade": "good" },
    "accessibility": { "title": "Accessibility", "score": "100", "grade": "good" },
    "best-practices": { "title": "Best Practices", "score": "100", "grade": "good" },
    "seo": { "title": "SEO", "score": "100", "grade": "good" }
  }
}
```

### Using the data in a template

Thanks to the magic of Eleventy data files, you can now loop over the scores via `lighthouse.categories` almost anywhere you like.

Most of my site uses the [Eleventy Vue plugin](https://github.com/11ty/eleventy-plugin-vue), but my footer is still using a Nunjucks file, so here's how I displayed the scores. I'm using Tailwind here for styling, but of course, you can style the output however you like - it's your website!

```html
<div class="flex flex-wrap items-center gap-2">
  <p id="homepage-lighthouse-scores">Homepage Lighthouse scores (%):</p>
  <ul
    class="flex items-center space-x-1"
    aria-labelledby="homepage-lighthouse-scores"
  >
    {% for key, result in lighthouse.categories %}
    <li
      class="flex items-center justify-center cursor-help rounded-full h-8 w-8 border-2 {{ 'border-green-300' if result.grade === 'good' else 'border-yellow-400' }}"
      title="{{ result.title }}"
    >
      <span class="sr-only">{{ result.title }}: </span>{{ result.score }}
    </li>
    {% endfor %}
  </ul>
</div>
```

…and here's how it looks!:

![Screenshot of a website showing Lighthouse test results represented by coloured circles. Each circle is green and shows a score of 100](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qku3xdbgyipwv2vx8bm1.png)

## Room for improvement

Here are some ideas for ways to adapt this to make it your own:

* My site is basically a single giant page, so I only bother to test one URL. Perhaps you'd like to swap the data file for a [template function](https://www.11ty.dev/docs/languages/javascript/#javascript-template-functions) so you can run an individual test for each page on your site by [using `this.page.url`](https://www.11ty.dev/docs/languages/javascript/#access-to-page-data-values)?
* My colour scheme for my footer is yellow so that's the default colour I've used for my rings. Even if I got a bad Lighthouse score, the rings would still be yellow, not red. If you are getting some low scores you should probably add some extra Nunjucks code to output a red border if `result.grade === 'bad'`, otherwise the yellow colour I am using would make it look like all your grades are 'ok'!

## Share this post

{% embed https://twitter.com/philw_/status/1503842798717620230 %}