How we got a 90-plus PageSpeed score on our 2020 annual report

In this technical deep-dive, our designer Zarino Zappia explores how we used the latest web performance techniques to make our 2020 annual report quick and easy to download — no matter what kind of connection or device you’re using.

Each year, mySociety produces an online annual report sharing a round-up of our achievements, a thank you to our supporters, funders, and volunteers, and our vision for the immediate future.

For the last two years, that annual report has taken the form of a single, multi-section web page.

This year, with more news than ever to cover across both mySociety and our commercial subsidiary SocietyWorks, we knew it was super important to get that single page running as smoothly and efficiently as possible in visitors’ web browsers.

Our annual report is often shared as an introduction to the work mySociety does. It’s crucial that it sets a good first impression, and performance is a big part of that. It’s also an embodiment of what sets us apart from the crowd – we sweat the small stuff, to give users the best possible experience.

What’s a PageSpeed score anyway?

Google has done a lot of work championing fast loading websites, and their PageSpeed Insights tool is often used as a benchmark of web performance. Developers aim to achieve a mobile performance score of 90 or more – but often this is easier said than done!

In this fairly technical blog post, I’m going to go through a few of ways we really optimised the performance of our 2020 annual report, to achieve a consistent 90-plus PageSpeed score. Through a combination of common sense, technological restraint, and a little extra effort, we manage to serve 6,500 words (a 33% increase over last year), 90 images, and two videos, over nine sections of a single page, in the blink of an eye.

Here’s how.

Keep it simple, stupid

Like most mySociety products, the annual report starts with a very boring, very unfashionable, but very fast base – plain old HTML – in this case generated by Jekyll. Jekyll helps to keep our work maintainable by letting us refactor common elements out into their own includes, but since everything compiles to plain HTML, we get lightning fast page renders from our Nginx web server. We use static HTML sites a lot at mySociety – for example, for our documentation, our prototypes, and even some of our user-facing products. Sometimes simple is just better!

Where we want to improve the user experience through interaction – things like hiding and showing the menu, and updating the URL as you scroll down the page – then we add small amounts of progressive enhancement via JavaScript. Again, like most mySociety products, the annual report treats JavaScript as a nice-to-have – everything would work without it, which means the browser can render the page quickly and our users spend less time looking at a blank white loading screen.

On the styling side, we use Bootstrap to help us quickly pull together the frontend design work on the annual report in about a week at the beginning of December. We build our own slimmed down version of Bootstrap from the Sass source, with just the modules we need. Libraries like Bootstrap can sometimes be a bit of a sledgehammer to crack a nut, but by cherry-picking just the modules you need, you can dramatically reduce the size of your compiled CSS, without losing the flexibility and fast prototyping that a library like Bootstrap provides. The annual report makes heavy use of Bootstrap’s built-in components and utility functions, and then anything truly bespoke is handled by a few of our own custom styles laid on top.

Use the tools at your disposal

Performance tweaks that were cutting edge a few years ago have quickly gained widespread browser support, so if you’re not using them, you’re missing out. Most of them take pretty much no effort to set up, and will dramatically improve perceived loading time for your users.

As a single, big, long page with almost 100 image tags, it’s imperative that we use lazy loading, to prevent the browser requesting all of those images at once on initial page load. Doing this is as easy as adding loading="lazy" to the image tags, but we also add a JavaScript fallback for browsers that don’t support the loading attribute, just to be safe. We even wrap it up in a nice Jekyll include, to make it super easy when managing the page content.

We minify our CSS and JavaScript files, and the server is set to gzip them in transit. These things are really easy to forget, but can make a big difference to load time of a page full of static files.

We also make an effort to efficiently encode our images – serving images at roughly the size they’re actually displayed, to reduce wasted bandwidth, and using WEBP compression with JPEG fallbacks, for the best balance of quality and filesize.

Reduce run-time dependencies

A big part of webpage performance is all the stuff that happens after the browser starts rendering the page. Loading external JavaScript libraries, tracking services, logging services – it all imposes a cost on your users. For some larger projects, you might decide the benefits for your team and the health of the product outweigh the network and performance cost for your users. But for the annual report we did the typically mySociety thing of reducing, reducing, reducing, to the simplest possible outcome. For example…

We don’t load any social media JavaScript. Data is patchy on whether people actually use social sharing buttons anyway, but just in case, where we do display sharing buttons, we use old-fashioned sharing URLs that just open in a new tab. Rather than embedding tweets using Twitter’s JavaScript embed API, we build our own tweets out of HTML and CSS. Not only does this mean we’re avoiding having to load megabytes of third-party JavaScript on our page, but it also helps protect the privacy of our visitors.

Sometimes you can’t avoid third-party JavaScript, though. For example, YouTube embeds. What you can do is defer loading the third-party JavaScript until right before it’s needed. In the case of YouTube embeds on the annual report, we use a little bit of our own JavaScript to delay loading any YouTube code until you click on a video thumbnail. To the end user, the entire thing happens instantaneously. But we know we’ve saved a whole lot of time not loading those iframes until they were really needed.

Unusually for a mySociety project, we don’t even load jQuery on the annual report. While jQuery still makes sense for our bigger products, on something as simple as our annual report, it’s just not worth the filesize overhead. Instead we write modern, efficient JavaScript, and include small polyfills for features that might not be present in older browsers. It makes development slightly slower, but it’s worth it for a faster, smoother experience for our visitors.

Nothing’s perfect!

Overall, some sensible tech decisions early on, and a lot of hard work from the design team (including our new hire, Clive) resulted in a 2020 annual report I think we can be really proud of. I hope it’s also a taste of things to come, as we start introducing the same techniques into our other, bigger products.

That said, there’s still a few things we could improve.

We could achieve almost instant rendering on first pageload by extracting the CSS required to display the first few hundred pixels of the page, and inlining it, so it renders before the rest of the styles have been downloaded. We already do this on, for example, using Penthouse.

There’s also an external dependency we could drop – Google Fonts. We’re currently using it to serve our corporate typeface, Source Sans Pro. The advantage of loading the typeface off Google Fonts is that most of our other sites also load the same typeface, so chances are the user will already have the font files in their browser cache. Self-hosting the fonts is unlikely to be much faster than loading from Google, but it would be a win for user privacy, since it’s one less external resource being hit for each pageload. Something to consider for future!



Photo by Indira Tjokorda on Unsplash