My sites are quite fast. Here’s a real-world mobile page speed report for my main site from PageSpeed Insights:
This is despite the fact that my site is built using Elementor, a WordPress page-builder plugin that is quite powerful… but also notorious for making websites slow.
I’ve been able to get around that using quite a few page speed tricks that I’ve picked up over the years. In this guide, I’ll try to share them all with you.
Note: I build all of my sites in WordPress. I think it’s still the all-around best platform for building a content-heavy website, despite all the competition. However, if you use other tools like Ghost, WebFlow, or even build custom sites with frameworks like Next.JS or Astro, you’ll still be able to benefit from a lot of this guide.
I believe the “holy grail” of site speed would likely be a headless WordPress site using Astro for its front end (of course, your back-end could then be anything – Strapi, Contentful, Markdown files, etc), with many of the additional optimizations I’ll describe below. But that requires a lot of tech know-how and coding; for now, I’m still on Elementor and getting good results.
Page Speed Fundamentals
Page speed comes down to the following factors:
- User connection speed
- Server power – CPU speed, # of CPU cores, RAM
- Server proximity to user
- Page markup size and complexity
- Layout shift (e.g. images being resized from original dimensions, external fonts loading and replacing fallback fonts)
- Number of external assets fetched by page (CSS, JS, images, etc)
- Size and complexity of external assets fetched by page
Think of it like a restaurant:
- How complex is the dish to make?
- How fast can the kitchen make the dish?
- How many dishes can the kitchen make at once?
- How fast can the server get the dish to the customer?
A static HTML page that has been cached and stored on an Edge server very close to the user is much like a vending machine selling Cosmic Brownies. The user taps their card, presses a button, and viola – instant chalky brownie in their hand.
A complex web app – say, an e-commerce store – has many components that have to load each time a user sends a request. The content must be unique to the user (you can’t cache it), the initial page likely has to make many requests to the server and database, and the entire page must be built and served from the origin server.
This is much like a fine-dining restaurant, in which the customer orders a complex dish that must be made to order, from scratch. Multiple kitchen staff will be involved in making it, nothing is pre-packaged or prepared in advance, and the server must take it all the way from the kitchen to the customer’s table.
When thinking about page speed, the questions you’re really asking yourself are:
- How can I pre-package and pre-prepare more parts of each dish?
- How can I make my kitchen staff work faster? Can I hire more staff?
- How can I make each dish simpler and take fewer ingredients?
- Can I make the dish right at the customer’s table?
In other words:
- Get hosting that gives you server resources powerful enough to prevent slowdowns
- Don’t bloat your website or app with unnecessary code
- Reduce the size of images and other assets as much as possible
- Reduce the number of calls to external resources
- Cache anything that can be cached (e.g. instead of building pages from scratch on every visit, build them from scratch once and then serve that constructed HTML to everyone else. Do this until the content needs to change.)
- Try to deliver content from a server that is physically closer to the user than yours (this is called edge caching)
- Reduce layout shifts through clean code, faster-loading fonts, etc.
My Page Speed “Stack”
I build my sites on WordPress and have been doing so since 2009. Over the 14 years I’ve been using WordPress (yes, I’m older than Galactus), I’ve done a lot to test and tweak my page speed.
Here’s what I currently do.
First, the TL;DR:
- Site: Self-hosted WordPress install using Elementor Pro and a custom-built theme. Elementor only handles pages with custom layouts (like the Ultimate Brain page)
- Hosting/Server: Vultr HF instance, 2vCPU (3GHz+ Intel Xeon), managed through GridPane
- Server caching: Server-side Nginx Redis Page Cache and Redis Object Cache
- Edge caching: Cloudflare APO, Argo Smart Routing, Argo Tiered Cache
- Additional CloudFlare Settings: Image Resizing, Polish (lossy), Auto Minify (CSS, JS), Brotli, Early Hints, Enhanced HTTP/2 Prioritization, TCP Turbo, Page Rules with Browser/Edge Cache TTL set to 1 month for most pages (higher-priority rules set for specific pages/sub-domains that need lower TTL)
- WordPress plugins: Imagify (image compression and WebP delivery), FlyingPress (minification, font optimization, lazy loading, link preloading), Perfmatters (more preloading, enabling scripts on a page-by-page basis)
Site Tools
I’ll start this list out by saying that my choice of site-builder is not optimized for speed.
Elementor is an extremely popular drag-and-drop page builder for WordPress. It essentially turns WordPress into WebFlow, allowing non-coders to design responsive web pages with nearly any design they want.
I’ve been able to build a great-looking site that relies heavily on Elementor for many of its custom-designed pages:
However, Elementor has a reputation for being slow. They’ve certainly made a lot of strides toward fixing that in recent years, but the fact remains that Elementor is a very JavaScript-heavy tool that can’t rival a custom-coded, minimal page in terms of loading speed.
This brings me to the main lesson I have to teach when it comes to page speed:
Your foundation matters!
My friend Martin hosts his extremely simple personal site on a super-cheap HostGator plan, and he does almost nothing to optimize it. But it still loads quickly, because:
- It’s coded from scratch
- Has extremely simple, minimal page markup
- Doesn’t load many external resources
Bottom line: Your page speed will benefit the most from a site that uses clean, minimal code. If you avoid adding bloated plugins, huge images, and features that require lots of resources, you shouldn’t have to do much to get a fast-loading website.
Unfortunately, there’s always a trade-off. Clean, minimal code often forgoes advanced features. Want a dynamic filtering tool on your hotel-booking site? That’s going to require more complex code.
Additionally, the more complicated features you want to add, the more the custom-code route will cost you – either in the time it’d take to learn how to code, or in money spent hiring a pro developer.
The alternative path, which I’ve taken for the time being, is using a tool like Elementor to create a site with the design and advanced features I want. Elementor is fairly easy to learn and quite cheap (the base version is free), but the trade off is that it’s a mass-market tool that can’t hope to match the elegance and optimization of fully bespoke code.
This means that we have to look to other optimizations we can make in order to improve our page speed – which I’ll now detail for you.
Server
I manage all my sites through GridPane, which is a platform that lets me deploy servers from multiple providers (Vultr, Linode, Digital Ocean, Google Cloud, etc). It has scripts and tools that will automatically set servers up with WordPress-specific settings. It also lets me easily set up new WordPress sites on those servers.
My main sites, managed through GridPane, are hosted on a $24/mo Vultr High-Frequency instance:
- 2 vCPU, 3GHz+ Intel Xeon
- 4gb RAM
- 3TB bandwidth
- 128gb storage
I host multiple high-traffic sites on this instance (both and College Info Geek), and the combined traffic and demand is nowhere near enough to challenge those server resources.
I have previously hosted on HostGator (2009-2016), Synthesis (2016-2018, no longer active), and WP Engine (2018-2022). Of those, I was most dissatisfied with WP Engine, which charged me $100/mo and set ridiculously low caps for visitors, storage, PHP workers, and more. I also experienced a significant improvement in both front-end and back-end WordPress performance when I switched to GridPane/Vultr (which is much cheaper!)
If you’re curious to learn more about WordPress hosting, I’d recommend the guides at Online Media Masters. Most hosting lists are heavily biased by affiliate programs; this one is not. Tom is one of the most open (and nitpicky) people in this niche, so I trust his recommendations.
Caching
My sites are mostly static. I don’t currently run a self-hosted ecommerce site or community (I rely on Lemon Squeezy and Circle for those, respectively), so the vast majority of the content on my sites is static and doesn’t need to change often.
This means that I can (and do) heavily cache my site content.
Caching means constructing a web page once, then serving that pre-constructed web page to everyone who visits your site.
Caching isn’t usually necessary for simple HTML files. But most modern sites aren’t so simple. When you load a page for the first time from WordPress, SquareSpace, WebFlow, or any other modern platform, that page has to be constructed using pieces from multiple sources.
The page might call for external JavaScript libraries, blog post content from a database, etc. So the server has to fetch all those resources (e.g. handle requests) and send them to the page so it can be constructed.
These requests take time, and they can take a long time if the server is getting hit with lots of requests (from lots of concurrent traffic, for example) and isn’t beefy enough to handle them all at once.
That’s where caching comes in. If I’m serving up a blog post, that’s content that isn’t going to change very often.
Instead of making my server fetch the post content from the server for every visitor, why not just fetch it once, embed it directly into the HTML, and serve the single HTML file to each visitor instead?
That’s exactly what I do.
There are many caching plugins for WordPress. I’ve used FlyingPress, WP-Rocket, and WP Super Cache – and there are lots of others, like Nitro.
Many of these contain other features, such as JS and CSS minification and deferral, lazy loading, local caching of fonts, etc. I still use FlyingPress for these reasons, though I have disabled its caching features.
This is because GridPane offers excellent caching options directly at the server level. I currently use:
- Redis Page Cache
- Redis Object Cache
You can read more about these here:
GridPane staff recommend disabling plugin-based caching when you use these server-caching options, which is why I have done so.
Image Optimization
I use the Imagify plugin to automatically compress every image that gets uploaded to my site. I also have it set to display images in WebP format by default. WebP is a newer image format with a much better compression algorithm compared to JPG, PNG, etc. Some older browsers don’t support it, so JPG/PNG images will be served as a fallback.
Image optimization is a huge factor in web page speed. I can’t emphasize it enough. Optimizing images is very easy to do, but I still see a lot of people uploading full-resolution, 5mb+ images to sites that would do just fine with a 1600px-wide, compressed version.
Minification
CSS and JS files are often bigger in file size than they need to be. This is due to the structure of the code – lots of new lines and indentation – as well as the presence of comments.
These elements make code easy to read for programmers, but they increase the size of each file and make them load more slowly.
Minification tools strip out comments and re-format these files into single lines of extremely long code. This helps them load more quickly.
I use FlyingPress for this (affiliate link):
Deferral
Plugins like FlyingPress and WP-Rocket can also defer the loading of non-essential CSS and JS.
Imagine you have a contact form at the bottom of a web page, and it relies on some external JS. You don’t need that JS right away when the page is loading, so why not load it a second or two later?
That’s what deferral does. However, you’ll notice that I don’t currently use deferral on my sites; the reason for this is because blanket-deferral can cause issues with JavaScript-heavy page builders like Elementor (which I use).
That being said, deferral can be very powerful if you can set it up in a way that doesn’t accidentally defer critical assets when the page loads.
Page-by-Page Enabling of Scripts
Many plugins and tools add JS scripts to your site’s header, which means they load on every page. For some scripts, this is necessary. External fonts, page-builder tools, JQuery, etc. all need to load on nearly every page.
But for other plugins – a contact form plugin, for example – you only need to load the scripts on pages which contain a contact form!
This is what the Perfmatters plugin allows me to do:
It’s got a host of other features, but the main thing I use it for is making sure niche scripts only load on the pages where I need them.
CloudFlare
CloudFlare is an amazing tool that offers a huge number of benefits. I use it on all my sites. It gets you (among other things):
- Tons of caching and speed optimization tools
- Security tools – DDoS protection, firewall, bot detection
- DNS management tools
When it comes to page speed, CloudFlare’s main benefit is edge caching.
Edge caching is a practice where your web pages – or at least some of the assets within them (images, JS, CSS) – are stored on many different servers.
When a user goes to your site, those assets will be loaded from the server that is physically closest to them. This is often referred to as delivering assets “from the edge”.
Bigger companies go a whole lot further with this, even doing “edge computing” where dynamic operations take place at the edge instead of an origin server.
But for us solopreneurs and small business owners, edge caching is much more accessible and easier to set up.
Now, why would you want to deliver assets from the edge?
Well, remember that electronic signals can only travel as fast as the speed of light – and often, they travel much slower.
If your site assets are only served from the server on which they’re hosted, that means they “live” at a single location.
Say your site’s server is in Hoboken, New Jersey. If someone in Sydney, Australia wants to hit up your site, the server has to send assets more than 9,000 miles!
And it’s actually further than that, since there isn’t a single cable stretching all the way from Hoboken to Sydney. There will inevitably be a huge series of routers and switches, creating a more roundabout path.
And of course, the user’s initial request has to travel all the way from Sydney to Hoboken in the first place. So there’s a whole round-trip to consider.
Now imagine that you’re using edge caching. Instead of your web assets only being hosted in New Jersey, they’ve been copied to servers all around the world.
When our friend in Sydney requests your web page, there’s already a copy of it at a data center right in Syndey! This means our friend sees your web page much faster.
The gist of this is that CloudFlare’s Automatic Platform Optimization for WordPress enables CloudFlare to server your entire WordPress site from the edge – not just specific assets.
In addition to APO, I’ve also enabled Argo Smart Routing and Argo Tiered Cache.
I’ll note that my use of Argo makes my CloudFlare bill around $130 each month. I’m a page speed nut, but I’d recommend working on all the other optimizations detailed in this guide before testing out the Argo features.
I also pay for CloudFlare Pro on each of my domains, which comes with additional benefits (primarily the Web Application Firewall for added security and additional page rules).
Aside from APO and Argo, I do a few other things with CloudFlare. First, I’ve enabled several of their speed optimizations – though not all, as some (particularly Rocket Loader) break my site. Assume that if an optimization from the Speed page isn’t listed here, I’m not using it.
- Image resizing
- Polish (Lossy, with WebP) – may be redundant with FlyingPress, but I haven’t noticed a negative effect
- Auto Minify (JS and CSS) – may be redundant with FlyingPress, but I haven’t noticed a negative effect
- Brotli
- Early Hints
- APO
- Enhanced HTTP/2 Prioritization
- TCP Turbo
Finally, I’ve set up several Page Rules within CloudFlare. Page Rules have priority levels; the lower the number, the higher the priority.
Here, you can see that I’ve set my entire domain to have both a Browser and Edge cache TTL (Time to Live) of a full month.
Then, I’ve set up higher-priority rules for specific pages that need a briefer TTL. For example, my /articles
page needs to get refreshed when I post a new article. So I keep its TTL at one hour.
Misleading Mobile Lighthouse Results
Why mobile Lighthouse tests are misleading:
Here, we can see that the real-world results from the Chrome UX Report paint a much better picture than the mobile Lighthouse test below them.
Chrome UX Report samples load times from actual, opted-in users. It creates a large dataset.
Meanwhile, mobile Lighthouse does a single load of the page on simulated Moto G4 Android phone (released in 2016), which is on a simulated “slow 4G” connection.
To point out just one stat difference, we see LCP listed at 5.9s on the Lighthouse test, but only 2.2s on the real-world test! That’s essentially the difference between “totally fine” and “unacceptable”.
Would I like to see <1s LCP for my site on mobile phones? Absolutely, and I’ll keep pushing for it. But it’s helpful to see that things aren’t as grim as the Lighthouse report makes them seem.
Here, I’ve run the same test on Amazon’s homepage:
Once again, we see LCP cut in half on the real-world report.
More Page Speed Resources
Here’s a collection of helpful resources I’ve found for optimizing page speed.