WordPress
WordPress Performance: Caching, Images, and Core Web Vitals
The three levers that move WordPress performance the most — page and object caching, image delivery, and script load order — in the order we actually fix them on client sites, with the tradeoffs at each step.

"WordPress is slow" is almost never true as a blanket statement — it's a proxy for one of three specific, fixable problems: no caching layer doing real work, images shipped at the wrong size and format, or JavaScript blocking the page from becoming interactive. We see all three on nearly every WordPress performance audit we run, and fixing them in the wrong order wastes effort — optimizing images on a site with no page cache still hits the database on every request; deferring scripts on a site serving 2MB hero images barely moves LCP. Here's the order that actually works, and why.
Fix caching first: it has the highest ceiling
Page cache is the highest-leverage single change available on most WordPress sites. For anonymous (logged-out) visitors, serve pre-rendered HTML directly, skipping PHP execution and database queries entirely. Whether this comes from a plugin (WP Rocket, W3 Total Cache) or host-level caching (many managed WordPress hosts run this by default), the effect is the same: a request that used to take 400–800ms of server processing drops to single-digit milliseconds because nothing is actually computing anything — it's serving a static file.
Object cache is the second layer, and it's the one people skip because it requires a bit more setup — Redis or Memcached, either self-hosted or provided by your host. This caches the results of expensive database queries (which posts belong to which category, what a specific query returns) so repeated lookups don't hit MySQL every time. This matters most for pages page cache can't fully cover — logged-in views, WooCommerce cart/account pages, anything dynamic per visitor.
CDN sits in front of both, caching static assets and (where applicable) full pages at edge locations close to the visitor. This is the layer that fixes Time To First Byte for geographically distributed traffic — a visitor in Singapore shouldn't be waiting on a round trip to a server in Virginia for every request.
The honest sequencing: enable page cache first, always — it's the highest-impact, lowest-effort change available. Add object cache when logged-in or dynamic traffic is a meaningful share of load. Add a CDN when your audience is geographically distributed or when asset delivery (not server processing) is the bottleneck you're chasing next.
Images: the second-highest ceiling, and the one most sites get wrong
Unoptimized images are the single most common cause of poor LCP we see in audits — a hero image shipped at its original upload resolution, uncompressed, in JPEG when WebP or AVIF would be 30–50% smaller at equivalent visual quality.
What actually needs to happen, in order of impact:
- Resize and compress at or before upload — an image optimization plugin or your host's image service should handle this automatically, but verify it's actually running rather than assuming; we've found sites with an optimization plugin installed and inactive more than once.
- Serve modern formats (WebP/AVIF) with fallback — most image plugins and CDNs do this via content negotiation automatically once enabled; it's rarely more than a settings toggle, and it's consistently one of the highest-return, lowest-effort fixes available.
- Set explicit width and height on every image — this is what prevents layout shift (CLS) as images load; WordPress's native image handling does this correctly by default, but page builders and custom theme code sometimes strip these attributes, which is worth checking directly in rendered HTML, not just assuming the CMS handled it.
- Lazy-load everything below the fold, and nothing above it. Native
loading="lazy"(or a lightweight library if you need more control) for below-the-fold images is a clear win. Lazy-loading the LCP image itself is a common, subtle mistake — it delays the exact element Google measures for LCP, actively working against the metric you're trying to improve.
One consolidated image solution beats several stacked ones. We've inherited sites running two image optimization plugins simultaneously (each installed by a different developer, neither aware of the other), which wastes processing and sometimes produces worse output than either alone. Pick one, configure it properly, remove the rest.
Scripts and fonts: the third lever, and the one most tied to plugin choices
Render-blocking CSS and JS delay first paint directly. Inline critical, above-the-fold CSS; load everything else asynchronously or deferred. Most caching/performance plugins offer this as a setting (critical CSS generation), though it needs testing per-template since auto-generated critical CSS occasionally misses content that should have been included.
Non-critical JavaScript — analytics, chat widgets, marketing pixels — should load after the page is interactive, not blocking it. Defer or delay these until after user interaction or on idle, using a tag manager's built-in triggers or a script-loading plugin. This is consistently the biggest INP lever on marketing-heavy WordPress sites, because these third-party scripts are numerous, individually small, and collectively responsible for most main-thread contention.
Fonts — self-host web fonts rather than loading from a third-party font service where avoidable, since that adds a DNS lookup and connection overhead for a render-blocking resource. Subset fonts to the character sets actually used, and use font-display: swap so text renders in a fallback font rather than staying invisible while the custom font loads.
Plugin bloat: the compounding factor across all three
Every plugin can add database queries (hurting caching's effectiveness), unoptimized images or asset handling (hurting the image layer), and script tags (hurting render-blocking and INP) — which is why plugin hygiene (see our dedicated guide) is really a performance topic as much as a security one. Profile with Query Monitor or a similar tool periodically to find slow queries and duplicate asset loads; it's common to find two plugins both enqueueing the same library, or a plugin running a heavy query on every page load when it's only needed on one template.
The order, condensed
| Priority | Fix | Typical impact |
|---|---|---|
| 1 | Page cache (plugin or host-level) | Largest single win — removes PHP/DB from most requests |
| 2 | Object cache (Redis/Memcached) | Fixes dynamic/logged-in page performance |
| 3 | CDN | Fixes TTFB for distributed traffic |
| 4 | Image resize + modern format + correct dimensions | Usually the biggest LCP and CLS win after caching |
| 5 | Lazy-load below fold (never the LCP image) | Reduces initial payload without hurting LCP |
| 6 | Defer non-critical JS, self-host/subset fonts | Primary INP lever |
| Ongoing | Plugin audit, query profiling | Prevents the other five from regressing over time |
Why the order matters more than the individual fixes
Any one of these fixes in isolation moves the needle a little. Done in the wrong order — say, obsessing over font subsetting on a site with no page cache — the effort-to-impact ratio is poor, because you're optimizing a layer that's dwarfed by a bigger, unaddressed problem underneath it. Done in this order, most WordPress sites reach "good" Core Web Vitals across the board within a focused week of work, not a quarter-long project.
This is the exact sequence we run on every WordPress performance engagement, whether it's a fresh build or an existing site that's accumulated years of plugin and content debt. If your WordPress site feels slow and you're not sure which of these three layers is actually the bottleneck, get in touch — an audit tells you which one to fix first, instead of guessing.