Speeding up WordPress without breaking it
Most slow WordPress sites are slow for the same four reasons: uncached pages on weak hosting, too many plugins loading assets everywhere, heavy page builders, and oversized images. Fix them in that order and measure after each step.

WordPress itself is not slow. A fresh install with a lightweight theme on decent hosting serves pages quickly. Real WordPress sites get slow because of what is added around the core: hosting that cannot keep up, dozens of plugins each loading scripts on every page, page builders that generate enormous HTML, uncompressed images, and a growing collection of third-party tags. Each of those has a fix, and the order in which you tackle them matters, because the first two usually produce most of the gain.
Before changing anything, take measurements you can compare later: TTFB and Core Web Vitals for the home page, a typical post and a typical product or landing page, on mobile. PageSpeed Insights gives both lab and field data. A DeskSEO check records TTFB and HTML size for the home page on every run, which makes the before-and-after easy to see.
Step 1: server response time and page caching
Without caching, every page view runs PHP, loads WordPress and all active plugins, and queries the database. On shared hosting under load, that can take one to three seconds before the first byte. Nothing on the front end can compensate for that.
Page caching stores the finished HTML and serves it directly to the next visitor. It is the single largest speed improvement for most WordPress sites. Options, from simplest to most robust:
- Host-level caching: many managed WordPress hosts include a server cache. If yours does, use it and do not add a second caching plugin on top.
- A caching plugin that writes static HTML files served by the web server.
- A CDN with HTML caching in front of the site, which serves cached pages from locations close to visitors.
Check that caching actually works: request a page twice and compare TTFB, or look for cache headers in the response. Common reasons it silently fails: cookies set on every page (by some plugins or consent tools) that make the cache treat every visitor as unique, query strings from tracking parameters, and logged-in users testing the site and seeing uncached pages.
If uncached response times are very slow (above a second for a simple page), caching hides the problem but does not solve it. Visitors who miss the cache, crawlers hitting rarely-visited pages, and WooCommerce carts all still pay the full cost. The TTFB guide covers the server side further: PHP version, object caching and slow queries.
Step 2: plugins, measured rather than counted
The question is not how many plugins you have but what each one costs. Two kinds of cost:
- Back-end cost: database queries and PHP work on every request. A query-monitoring plugin (used temporarily, then removed) shows queries and time per plugin on a given page.
- Front-end cost: CSS and JavaScript files the plugin adds to pages. Many plugins load their assets on every page even if they are only used on one. A contact form plugin loading its scripts on all 400 blog posts is a typical example.
Work through the list:
- Deactivate and delete plugins nobody uses. Old sliders, abandoned social sharing tools and "temporary" helpers accumulate.
- Replace plugins that do something trivial with a few lines in the theme, or with a feature the theme or core already has.
- For plugins you keep, restrict asset loading to the pages that need them. Some plugins have this setting; asset management plugins can do it for others.
- Watch out for plugins that call external services on every page load (stock checkers, social counters, font services), which add network waiting time to server response.

Step 3: theme and page builder weight
Page builders let non-developers create layouts, at a cost: nested wrapper elements, inline styles, and large CSS and JavaScript bundles. A simple page can end up with several thousand DOM nodes and hundreds of kilobytes of assets. This hurts every Core Web Vital: LCP waits for render-blocking CSS, INP suffers from large DOMs and heavy scripts (see fixing INP), and CLS rises when sections load in stages.
Options, depending on how far you can go:
- Use the builder's own performance settings: many now offer optimised asset loading, reduced DOM output and disabling of unused widgets.
- Build simpler layouts. Fewer nested sections, fewer animations, no sliders above the fold.
- Use the block editor with a lightweight block theme for new pages, keeping the builder only where it is genuinely needed.
- For a redesign, budget for a lighter theme. It is often cheaper than months of optimisation.
Step 4: images
WordPress generates several sizes of each uploaded image and outputs srcset automatically, which is good. Common problems remain:
- Original uploads of 4000+ pixels used directly in content or as backgrounds. Set maximum upload dimensions and resize existing originals.
- JPEG and PNG only. Serve WebP or AVIF via a plugin, the host or an image CDN. The image SEO guide compares formats.
- The hero image lazy-loaded. WordPress adds
loading="lazy"to most images and tries to skip the first one, but themes and builders often defeat this. Check that the LCP image is not lazy-loaded; see fixing LCP. - Background images set in CSS for hero sections, which are discovered late.
Step 5: CSS and JavaScript delivery
Once the server is fast and the page lean, asset delivery is the remaining lever. Optimisation plugins offer many options; these are the ones that matter most, with their risks:
| Option | Benefit | Risk |
|---|---|---|
| Defer JavaScript | Scripts no longer block rendering | Inline scripts that depend on deferred libraries can break |
| Delay JavaScript until interaction | Large LCP and TBT gains for third-party scripts | Menus, sliders and forms may not work until the first tap; analytics can undercount |
| Critical CSS / remove unused CSS | Faster first render | Unstyled content flashes or missing styles on some templates |
| Minify | Slightly smaller files | Low; occasionally breaks badly written scripts |
| Combine files | Fewer requests | Mostly unnecessary over HTTP/2; can make caching less efficient |
| Preload fonts, local fonts | Faster text rendering, fewer layout shifts | Low |
Enable one option at a time, then test the pages that matter: the menu on mobile, forms, the cart and checkout if you have one, logged-in views and any interactive widget. Clear all caches between tests.
Step 6: third-party scripts
Chat widgets, heatmaps, multiple analytics tools, ad pixels, embedded social feeds and review widgets often weigh more than the site itself. List every external script loaded on a typical page (the network panel filtered by third-party domains), and for each ask who uses its data. Remove the ones nobody can name an owner for. For the rest, load them after the page is interactive, or only on the pages where they are needed. A chat widget loaded only on pricing and support pages, or only after the user clicks "Chat", costs nothing elsewhere.
Step 7: the database and background work
Over years, WordPress databases collect post revisions, expired transients, spam comments and orphaned data from deleted plugins. A bloated options table with large autoloaded entries slows every uncached request. Cleaning revisions and expired transients, and checking the size of autoloaded options, is worth doing once a year. Also replace the built-in WP-Cron, which runs scheduled tasks on visitor page loads, with a real server cron job on busy sites.
WooCommerce and membership sites
Shops and membership sites have pages that cannot be fully cached: the cart, checkout, account pages, and often any page for logged-in members. For these, server performance matters more than front-end tricks. A current PHP version, a persistent object cache such as Redis where the host supports it, and fewer plugins hooking into every request make a visible difference. Also check WooCommerce's cart fragments script, which on some setups makes an extra uncached request on every page to update the mini-cart; many shops can limit it to pages where the cart is shown.
Measure after each step
Repeat the baseline measurements after each step. Keep a short log: what changed, TTFB, lab LCP, total page weight and number of requests. Speed work on WordPress is full of options that help one site and hurt another, and the log is how you know which is which. Field data in Search Console takes up to 28 days to reflect improvements, so judge by lab data first and confirm with field data later.
Questions
How many plugins is too many?
There is no magic number. One badly written plugin can slow a site more than thirty good ones. What matters is what each plugin loads on the front end and what database work it does per request. Measure, do not count.
Will a caching plugin fix everything?
Page caching fixes server response time for anonymous visitors, often dramatically. It does not fix heavy images, too much JavaScript or layout shifts, and it does not help logged-in users or uncached pages such as carts and checkouts.
Is a CDN worth it for a small WordPress site?
Usually yes, especially if your visitors are spread across countries. Many CDNs have free tiers. For a site with a purely local audience and a well-located host, the gain is smaller.
Should I use a speed optimisation plugin that does everything?
All-in-one optimisation plugins can help, but their aggressive options (combining scripts, delaying JavaScript, removing unused CSS) can break features. Enable options one at a time and test forms, menus, checkout and logged-in views after each.
Keep reading

WordPress SEO setup: the settings that matter after install
WordPress is search-friendly out of the box, with a handful of exceptions that catch new sites every day. These are the settings to check a…

Fixing Largest Contentful Paint (LCP): a step-by-step diagnosis
LCP measures when the biggest thing on screen appears. Every slow LCP breaks down into four phases, and once you know which phase is slow,…

Server response time (TTFB): what is slow and how to fix it
TTFB mostly measures how long your server takes to build a page. Split it into network and server time with curl, then fix caching, queries…