Who is this session for?
Developers
Session description
The COVID-19 pandemic has forced countless teams to pivot to full-time remote work and schools across the country to move to online-only or hybrid in-person/online instruction. These changes have made it painfully obvious that not everyone, and perhaps not even most people, have access to high-speed broadband Internet connections in their homes. This has made it even more of an imperative that we optimize our websites and web applications for performance.
In this talk, I will walk you through how I optimized a Python/Flask web application to improve its Chrome Lighthouse performance score by nearly 50 points and how you can apply some of what I learned to your own WordPress development. You will leave with a concrete list of steps you can take to start making your site faster right away.
Presenter
Rachel Leggett
Rachel Leggett develops custom web applications for the University of Michigan College of Engineering. She also manages their WordPress multisite environment for administrative units, faculty, labs, centers, and academic departments across the College and has been using WordPress since 2009. In her free time, she loves to knit, sew, and dance.
Sessions
- General Lecture Session: Improving Website Performance: A Case Study and Steps You Can Take
Session video
Session transcript
Presenter: My name is Rachel Leggett. I am a senior Web applications developer . . . and I will talk to you about improving website performance.
I build custom Web applications for faculty, academic departments, and labs. Let's get going.
Today, we're going to talk about why it matters that your site is fast. We'll move into a case study. We'll talk about how . . . WordPress development, and then we'll summarize what we've learned.
So, a 2019 Microsoft study found that 162 million Americans lack broadband Internet, particularly in rural areas. That's nearly half the U.S. population. That could potentially be worse around the world.
With COVID having forced a lot of people to . . . and increasing remote work, people are increasingly distributed around the world. We cannot assume that everyone accessing the university content will have a fast connection. Even some of my coworkers in Ann Arbor, who are now working from home, have spotty Internet 25 minutes outside of Ann Arbor.
I thought Tim Wright made a good point that access to information is access to truth. Our website needs to be accessible in that. If they're slow, or they don't load at all, they are just as inaccessible.
It's also better for business. Faster websites have lower bounce rates.
Let's move into our case study. One of the apps I work on is called the Software Management Interface, or SMI. It displays information about software available to faculty and students, as well as limitations, like if it can be used on a virtual machine, or whether it can be used 50 miles outside of campus.
This website was always a bit slow, but nobody complained because most people accessed it from a university connection.
A year ago, people were working at home one day a week. Even on fast home broadband, it was painful how slow the site was, because nothing was as fast as the university connection.
On a more frustrating work day, it ran a Lighthouse test to see how bad it was. If you're unfamiliar, Lighthouse is a tool provided by Google Chrome, and it allows you to run audits on your sites. I use it for performance and accessibility testing.
And so, on the performance test that I ran — I have a screenshot showing our score was 26 out of 100. Sometimes, I saw scores as low as 0 out of 100, depending where I ran the test, like on what page in the app.
Some of the highlights of this very poor result are that it was taking . . . 7.6 seconds until the first meaningful ping or the first time that any actual useful content showed up on the page. And then, it took 8.8 seconds until it was interactive, until a user could use the page. Clearly, that is not acceptable.
So, we took a two-pronged approach to trying to fix this issue. We started with backend optimizations. We looked for inefficiencies in code. We moved to frontend optimizations and reduces the number of size of imports.
Next, we continue to reduce the file sizes and limit dependencies.
Lighthouse suggests great frontend improvements. There are links to documents on things like, what is a render-blocking resource? How can I limit it? But I knew there were problems with the backend, so I started there.
I asked a student employee to evaluate the app and make suggestions for backend performance. The app had to rebuild its database model on every page, the way it had been coded. We use Flask-SQLAlchemy. We were not leveraging Flask Sequel Alchemy to its full extent. Flask-SQLAlchemy has ORM, which can make it more developer-friendly to interact with the database through your app, and more efficient (like with joins).
We will talk about how the Flask-SQLAlchemy works. On the righthand side, we have a sequence where we declare a class. We're telling it this corresponds to the table name. We're telling it it has a composite primary key consisting of all three of its columns. This is a database design decision I was not a part of. I have taken this up with other people I work with.
We're specifying the primary key constraints and listing out the columns in the table. Are they foreign or primary keys? What type are they? A string, integer, or date time?
Something not shown that is great about this ORM is that you can specify how tables relate to each other.
From this angle, it makes it convenient to access a table two steps away in the relationship.
There is a lot of boilerplate to get this set up for the application. It was worth it. The student and I worked on refactoring it for several months. That got us about halfway to our optimization.
Next, we tackled opportunities for improvement on the frontend. As I mentioned, Lighthouse provides fantastic suggestions here. We decided to focus on reducing the number and size of our imports.
We had too many render-blocking resources. We were importing too many things, and they were too big, and they were all trying to load before the page loaded anything. That's why we saw seven seconds before we loaded the page.
So, we used more built-in HTML and JavaScript functionality. We modified JavaScript. We leveraged built-in HTML elements.
One example of this is, here at the admin side of the app, we have date pickers in a bunch of different forms. We were building those — we were using jQuery for those date pickers. I have a screenshot to the right of my slide with a sample date picker. You have a field that you can click in, or maybe you click a calendar icon or something, and it brings up a calendar that you can scroll through to select a date. Once you select the date, it properly formats it and enters it into your field.
The screenshot I have is Chrome's native implementation of the HTML input type equals date. We switched to using the built-in HTML element instead. That is supported on every modern browser. We were importing the entire library for date pickers.
As we replaced jQuery with JavaScript — jQuery became popular because it used less to do the same thing that plain JavaScript. It was difficult to add or remove a class, for example. jQuery added functionality for that. These days, JavaScript has that same functionality built in, in an easy way. You can use Document.GetElementById() to do what you wanted to do.
jQuery is such a big library, and we didn't want to import it. We haven't completely replaced jQuery yet.
We also removed unused CSS.
We now minify and bundle CSS and JS. Minifying is removing whitespace, or maybe we'll rename a variable to a shorter name so it takes us less space, less text in your file, I mean.
For that, we used a Flask library called Flask-Assets. We have an admin CSS file and an admin JavaScript file, as well as public. We minify into those four files.
We load only the admin files on the admin side and the public files on the public side. We have an admin interface with a ton of stuff that isn't used on the public side. That way, we can save time on the public side. It's okay if the admin folks have to wait a little longer. But the optimizations did speed up the admin side, too, I have to say.
After the optimizations, we're achieving a Lighthouse performance score of 63 out of 100. We have a performance score comparison on the left, a column chart. We have a column labeled "Before Optimization," which goes up to 27 out of 100. Then we have two columns stacked; the bottom score is the mobile score of 63, and the top is the desktop score of 97. It all adds up to 97. We were getting 26, and now we're getting 63 to 97.
The reason for the discrepancy before the mobile and desktop scores is that Lighthouse simulates a flaky 3G connection for the mobile audit. I placed more stock in the mobile score than the desktop score, because I want people with low-speed connections to access my site. Obviously, we're not done. The mobile score of 63 has a 3.7-second wait for content. This is 50 percent better than before. Over three seconds is still way longer than I would like. But we've made progress.
We still have opportunities for improvement. I want to finish the remaining jQuery dependencies. I also want to paginate results, or implement a "load more" option for pages taking longer to load. Especially on the admin side, pages show data — every software package ever offered for faculty and students, every version, in all of our computers in our computer labs. With all of the optimizations, these pages take a long time to load. But they don't need to load all of that data at one time. I want to look into mitigating that.
I learned a lot in that case study. You can apply those same types of approaches to WordPress. You can go for a minimal, just-in-time approach; you can use available tools to mitigate slowdowns.
Focus on a minimal, just-in-time approach. Try to ramie bloat/unused code. You can remove large dependencies and use micro-libraries to accomplish only what you need and not more.
Say there was a compelling reason that we needed some custom functionality or custom something in that date picker. Instead of importing all of jQuery UI to do what we wanted to do, we can write a small bit of JavaScript to do what we need to do; or, maybe there's a library that we can import to do what we need to do.
You can bundle and minify where possible. I used a Flask library for that, but there are other, more popular options out there. Webpack is big. I know Gulp was popular in the WordPress community for a while. Snowpack is another, newer one you might want to look into.
You can also load what you need and when you need it. Try not to load every script into the header. Only load the things you need on pages; don't load extra things. Load asynchronously where possible.
So, one thing that's hard to avoid when using WordPress, not WordPress in a decoupled architecture, is that third-party themes and plugins load CSS and JavaScript when it's not needed. If you only need some CSS on one page, only load it on that page. Plugins often load it on every page.
I use a plugin that I would recommend —
There are both free versions and premium versions. I have links to both in these slides, if you want to go back later. I use the free version. It's not an ad; I'm just a fan.
Hummingbird comes with several tools. Asset Optimization I find the most helpful. It is offered in the free version.
Here, I am displaying a screenshot from a personal website that only uses third-party plugins and for which I have not done custom coding.
Asset Optimization allows you to minify, exclude files, force them to load after the content, force them to load asynchronously, and it works with both CSS and JavaScript files.
Asset Optimization allows you to minify, exclude files, force them to load after the content, etc. It doesn't make suggestions for what to do, so you need to think carefully about what a file is used for, by looking at its name, etc. Also, refresh your site regularly, so you don't break anything.
Here, I have minified two theme files. I have moved an icon-style sheet to load in the footer, because that loads one tiny bar in a search bar. I am excluding two Jetpack files I don't know. I am only using it for widget visibility. I found this tool very helpful.
Another thing you can do is optimize images for big speed improvements. If you've waited for a ton of images to load or a large image to load, you know that images can be a big deal.
But there are things you can do. Make sure it supports WordPress's native responsive image implementation. When you add an image to your site, it adds the SRC and values to a tag, which tells the browser which image to load for which screen size.
You can extend that using a filter. I do not have that filter listed on this slide. If you look up how this works, I'm sure you can find it.
I recommend using a CDN, a content delivery network, a geographically distributed group of servers that work together to provide fast delivery of content.
I use Cloudflare, which has CDN and caching and DDOS mitigation, a good security bonus.
I like to use Cloudflare, which has amazing free features, which includes a CDN, caching, and a security bonus DDoS attack mitigation.
You can also service WebP images where possible. It's supported in new versions of Chrome, Firefox, Edge, and Opera. It is in the preview version of the next release of safari.
WebP is a modern image format with great compression. This is smaller than other formats.
WordPress requires a plugin to load WebP.
You can lazy-load your images, which is loading your images as they are needed. If you have a long news article with images throughout, the user shouldn't need to wait for the images at the end to load. Lazy loading implements that only the images in the viewport will load, and as the user scrolls, the other images will load.
Lazy loading has been merged to the release candidate for WordPress 5.5, coming in just a few weeks, in August. You could test that today.
If that sounds like a lot of work for image optimization, I have a no-code recommendation for you. I like the Smush plugin from WPMU Dev. I use the premium version of this one, because it comes with WebP support with a CDN, a pro feature of Cloudflare, and it's cheaper annually to do a premium subscription of Smush than Cloudflare. And I'm still just a fan, still not sponsored — darn.
If you are building a headless WordPress site . . .
Smush handles serving WebP on only supported browsers, so you don't have to worry if a user is using Internet Explorer.
I recommend caching your data and limiting API calls. I also recommend using a static site generator.
You're probably familiar that WordPress builds each page on demand. Usually, sites only change when there's a change to the content or the design, which isn't happening all the time.
The static site generators rebuild when something changes. And so, it's always the static files that your visitors request when they come to your site, which leads to faster performance.
If you're curious about this, and you missed the talk about decoupled websites yesterday, I recommended going back to that. It's a deep dive into one implementation of this. They use Gatsby, Hugo, Eleventy, and Nuxt. I believe Hugo is a Go framework for static site generators, and the others are JavaScript. Eleventy is probably plain JavaScript. There are a bunch of other options, too. There will be something that is right for you.
If you are using a static site generator, you might want to look into an image plugin.
And then, of course, accessibility should never be an afterthought. Refactoring and/or optimizing a Web app is a great chance to double-check its accessibility.
I check the labels to make sure they meet the WCAG2 standards. When you optimize images, that's a good time to check that they have descriptive alt-text. It's much easier to work on accessibility as you go.
In summary, I refactored the backend of my Python and Flask app to use an ORM. I minified and bundled JS and CS S files, removed unused SS, and rewrote jQuery-dependent functionality to use plain JavaScript and HTML to cut down on import size.
You can focus on a minimal, just-in-time approach to serving assets like scripts and images; optimize images by using responsive images, WebP, a CDN, and lazy loading; you can use no-code tools; you and can limit API calls. With a static site generator, you will see vast speed improvements.
Check for accessibility in every step of the process.
Here, I have my Twitter and e-mail if you have questions. Eric, are there questions in the Q&A?
Room Host: Rachel, thank you very much. That was a wonderful presentation. I bet your kittens are scheming in the background for that. We have questions that have popped up in the chat.
"When you say that you made it so admin files do not load unless the user is on administrative pages, what did you do to accomplish that?"
Presenter: That's a good question. I can't remember off the top of my head. We used Flask for our frontend framework. I can't remember if it was a built-in feature of Flask or Flask Assets. There was a conditional that were able to put in there.
Room Host: We have another question: "Do you have tool suggestions for finding and eliminating unused CSS? ϑ "
Presenter: No. You can only evaluate the page you're on in Chrome. I tried to use that when I started. If I found something that wasn't used on a page, I went to several other pages to see if it was used there. For the most part, I scrolled through the CSS to find things that weren't used. I tried to trace things back and see where they were used. That is why we're not done yet, but it is a very manual process.
Room Host: Thank you. Another question asks, "Does Hummingbird operate with things like W3 total cache?"
Presenter: I'm not sure. I am not sure how it works with other caching plugins out there.
Room Host: Okay, great. We received another question: "When you work on a full team with separate roles, it is hard to implement such optimizations. Do you have recommendations on how to get buy-in from all team members to get such optimizations implemented?"
Presenter: Hmm. That is a good question. I like to . . . Try to explain how their lives could be better, I guess, with more optimization. Also, talk about the business case for it. At the university, we might not be talking about profit, but we might be talking about how many people click through to learn about admissions and stuff like that.
I think it was Francesca yesterday who shared some data on how much bounce rates increase as page load times go above three seconds. I don't know the data, but that could be a place to start, demonstrating these are the facts of how people interact with the site when it is not optimized.
If you're comfortable, you could take the lead on those optimizations. You could take part in code reviews, and maybe don't merge poll requests that will make your performance worse.
There is something that I want to look into that I don't know whether it is possible or works — I think there are tools where you can reject a poll request if a performance score decreases. You can say you don't want to merge that because it's making the site slower.
Also, I don't know whether trying to convince every person on your team is the right choice. I think the most important to convince are the people in charge of making the decisions. If your manager is onboard, they probably have strategies for how to get the other folks on your team onboard, as well.
Room Host: Awesome. Thank you. Rachel, we received another question: "What was the most difficult or time-consuming aspect of optimization? Did you expect that to be the most time-consuming or difficult?"
Presenter: The most difficult is that this is in RDS and AWS. I have little experience with AWS besides the build scripts. Using Flask . . . I don't know if it was using Flask Assets or Flask Digest. One of those, I remember I had to make configuration changes to AWS as well as changes in our app initialization file. They were unexpected, and I had no idea what I was doing. I definitely collaborated with someone on our team with more experience with AWS. It was challenging. I ended up figuring it out without much assistance. I was proud, too. It definitely raised my confidence. It was hard.
Room Host: All right, we've got another question: "Speaking of manual optimization, a child theme would prevent theme updates from overriding those fixes. How do you deal with JavaScript and CSS optimization?"
Presenter: I think, um . . . Hummingbird would deal with that, but I haven't tried it on a child theme.
Room Host: Okay. We have three minutes left, so I invite anyone with additional questions to post with the "Ask a Question tool."
"It sounds like image optimizations were a big part of the design. Do image uploads have to take certain parameters into account when they upload?"
Presenter: That's a good question. I think that the usual advice of uploading images that are the right size for your theme or content still applies. In my experience, using Smush, they do their best to resize images to sizes that make sense, and to responsive sizes if they don't already exist.
You can tell it that you want to resize images above a certain size, to a certain maximum size.
It's probably just relying on Smush for uploading the right image size to begin with, is not a good idea. Probably still manually resizing the images before upload is the best plan.
Room Host: "Thoughts on SPGs?"
Presenter: Hmm. That's kind of board. I like them, I think they're good, it's good that they scale. I don't think WordPress allows you to upload SPSs; you have to install a plugin. The more plugins, you have to worry about them injecting code you don't want.
SPGs are good when people know how to use them.
There was a talk yesterday where someone demonstrated making a chart that was accessible. Without more direction on that question, that's what I've got.
Room Host: That's quite all right. Thank you.
Rachel, we are about out of time. For additional questions that pop up, I'd like to point folks to the WPCampus Slack to continue asking them. Rachel, you can use the "Ask a Question" tool to help answer those questions.
Thank you for your presentation today. You handled it with grace, and your presentation was full of expertise.
Presenter: Thank you. Thank you for all those questions. I am happy people were engaged in the presentation.