You've read the standard advice: compress images, enable caching, minify CSS. Your site is still slow. In support tickets I've handled over the years, the problem is rarely that people don't know what to optimize—it's that they've applied the fixes incorrectly or in the wrong order.
This article catalogues the nine most common mistakes I see when people try to speed up their sites, and the correct approach for each one.
Mistake 1: Testing on a fast connection from a nearby location
You load your site on fiber from the same city as your server. Load time: 800ms. You're satisfied. Your visitor in rural Australia on 4G sees a five-second blank screen.
Speed is relative. Testing only from your own network gives you false confidence because you're measuring best-case conditions: low latency, high bandwidth, minimal packet loss.
The correct approach
Use a synthetic testing tool that simulates different connection speeds and geographic locations. WebPageTest lets you choose from cable, 3G, LTE profiles and test from data centers worldwide. GTmetrix and Google PageSpeed Insights run tests from specific regions.
Run at least three tests from different continents if you have a global audience. Pick the worst result as your baseline—that's the experience you need to fix.
Test on a throttled mobile connection too. Chrome DevTools has network throttling built in. Set it to "Slow 3G" and reload your page. You'll see what most of the world sees.
Mistake 2: Enabling every caching plugin at once
WordPress users especially do this. They install W3 Total Cache, WP Super Cache, and a CDN plugin simultaneously, flip every toggle to "on," then wonder why the site breaks or serves stale content.
Caching layers can conflict. Browser cache headers fight with server-level caching. Plugin-generated cache fights with Cloudflare cache. One layer invalidates while another serves old markup.
The correct approach
Pick one caching strategy and configure it properly before adding another layer.
Start with server-level caching if your host supports it—many managed WordPress hosts have built-in object caching and page caching that's faster than any plugin. Check with your host first.
If you need a plugin, pick one and learn its settings. W3 Total Cache is powerful but complicated; WP Rocket costs money but works out of the box. Don't run two page cache plugins at the same time.
Add a CDN only after your origin server is fast. The CDN will just distribute slow pages faster otherwise.
Set cache expiry times deliberately. Static assets (images, CSS, JS) can cache for a year. HTML should cache for minutes or hours depending on how often you publish. API responses might cache for seconds.
Mistake 3: Compressing images after upload
You upload a 4MB photo, then run it through an optimization plugin. The plugin shrinks it to 400KB. Better, but you've already stored and possibly served the 4MB version.
Plugins that compress on the server also burn CPU and I/O every time they process an image. On shared hosting with tight resource limits, bulk optimization jobs can max out your quota and trigger throttling.
The correct approach
Resize and compress images before you upload them.
For batch work, use tools like ImageOptim (Mac), FileOptimizer (Windows), or command-line tools like jpegoptim and optipng. Process your entire image library locally, then upload the compressed versions.
# Compress all JPEGs in a directory (Linux/Mac)
find . -name "*.jpg" -exec jpegoptim --max=85 --strip-all {} \;
# Compress PNGs
find . -name "*.png" -exec optipng -o5 {} \;
For photos, save as progressive JPEG at 80-85 quality. That quality level is visually lossless for web use and cuts file size by half compared to maximum quality.
For graphics with transparency, WebP beats PNG on file size while preserving quality. Most browsers support it now; you can serve WebP with a PNG fallback using the <picture> element.
Mistake 4: Letting your theme load fonts from Google
Your theme pulls three Google Font families with eight weights each. That's multiple DNS lookups, TCP handshakes, and render-blocking requests before the page can paint text.
External font requests add latency you can't control. Google's CDN is fast, but every third-party domain adds overhead.
The correct approach
Self-host your fonts or cut down the number of weights.
If you need two font families (one for headings, one for body), download only the weights you actually use—typically regular (400) and bold (700). Skip light, medium, semi-bold unless your design genuinely needs them.
Use the font-display: swap CSS property so text renders immediately in a system font while the web font loads. This eliminates the flash of invisible text (FOIT) that makes pages feel slower than they are.
@font-face {
font-family: 'YourFont';
src: url('/fonts/yourfont.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
WOFF2 format is well-supported and compresses better than TTF or WOFF. Convert your fonts to WOFF2 and serve only that format with a system font fallback in your CSS stack.
Mistake 5: Minifying before concatenation
You minify ten JavaScript files, then try to combine them. The minifier has already stripped out whitespace and renamed variables, making the combined file harder to debug when something breaks—and something always breaks.
Or you concatenate files without understanding dependencies. jQuery plugins load before jQuery. Modules reference functions that haven't been declared yet. The site breaks silently on mobile.
The correct approach
Concatenate first, minify last, and test the result.
For small sites, manually combine your scripts in dependency order, then minify the bundle. For larger projects, use a build tool (webpack, Rollup, esbuild) that handles dependency graphs automatically.
Keep one unminified version in your source control. Minify only for production. When you need to debug, test against the readable version.
With HTTP/2 and HTTP/3, concatenation is less critical than it was. Multiple small files can transfer in parallel over a single connection. You still want to minify, but don't obsess over combining every script into one giant bundle.
Mistake 6: Lazy-loading above-the-fold images
You enable lazy-loading on every image. Your hero banner—the biggest visual element at the top of the page—now loads late. Visitors see a blank rectangle for two seconds while JavaScript decides to fetch the image.
Lazy-loading is for content below the fold. Applying it to everything delays the largest contentful paint (LCP), which is the metric Google uses to measure perceived load speed.
The correct approach
Lazy-load images that appear below the initial viewport only. The first two or three images—especially your hero image or logo—should load immediately.
In HTML, use the loading="lazy" attribute for images that start off-screen:
<img src="hero.jpg" alt="Hero image" /> <!-- loads immediately -->
<img src="feature.jpg" alt="Feature" loading="lazy" /> <!-- deferred -->
Most browsers support native lazy-loading now. You don't need a JavaScript library for this anymore.
For WordPress, many cache plugins and optimization plugins let you exclude specific images from lazy-loading. Add your header logo and hero image to the exclusion list.
What about render-blocking resources?
CSS and JavaScript in the <head> block rendering. The browser downloads them before it can paint anything. That's what "render-blocking" means.
The common advice is to defer or async everything. People do that, and their site flashes unstyled content or interactive elements stop working.
Mistake 7: Deferring critical CSS
You move all CSS to the footer or load it asynchronously. The page renders with no styles for a second—ugly default Times New Roman, blue underlined links, broken layout—then snaps into the design once the stylesheet arrives.
That flash of unstyled content (FOUC) looks broken. It hurts trust. Visitors bounce because they think the site is malfunctioning.
The correct approach
Inline critical CSS and defer the rest.
Critical CSS is the minimum styling needed to render above-the-fold content. Extract those rules (layout, typography, colors for the header and hero) and put them in a <style> block in the <head>. That CSS is tiny—usually under 10KB—so it loads instantly with the HTML.
Load your full stylesheet asynchronously afterward:
<style>
/* Critical CSS here */
</style>
<link rel="preload" href="/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/style.css"></noscript>
Tools like Critical or Penthouse can extract critical CSS automatically, but manual extraction works fine for small sites. Copy the rules for your header, navigation, and hero section into an inline style block.
Mistake 8: Hosting images on the same slow server
Your site is on a basic shared hosting plan with limited bandwidth. You embed fifty high-res images. Every image request competes for the same limited resources as your HTML and database queries.
Shared hosting often has per-account bandwidth caps and throttles connections when you exceed them. Serving large media files eats that quota fast.
The correct approach
Offload images to a CDN or object storage.
Most CDN providers—Cloudflare, BunnyCDN, KeyCDN—cache images at edge locations close to your visitors. The first request pulls from your origin; subsequent requests serve from cache. That reduces load on your hosting account and speeds up delivery.
If you're on WordPress, plugins like Jetpack or Cloudinary can automatically serve images from their CDN. Configure the plugin, and image URLs rewrite to the CDN domain.
For static sites or custom apps, upload images to S3-compatible object storage and reference them by CDN URL:
<img src="https://cdn.yourdomain.com/images/photo.jpg" alt="Description" />
Object storage is cheap—usually a few dollars per month for hundreds of gigabytes. It's built for serving files at scale, unlike a $5 shared hosting account.
Mistake 9: Ignoring database query performance
Your page renders fast on a fresh install. Six months later it takes four seconds. You've cached everything. The slowdown is invisible because it's happening in the database.
WordPress and other CMS platforms run dozens of queries per page load. Poorly indexed tables, bloated options, and unoptimized plugins stack up. Eventually the database becomes the bottleneck.
The correct approach
Profile your database queries and optimize the slow ones.
Enable query logging temporarily to see what's running. In WordPress, the Query Monitor plugin shows every query, execution time, and which plugin triggered it. Install it, load a slow page, and sort by query duration.
Look for queries taking over 100ms. Common culprits:
- Plugins scanning the entire posts table without a WHERE clause
- Options queries that pull huge serialized arrays
- Postmeta lookups without indexes on
meta_key
Add indexes where needed. If a plugin repeatedly searches wp_postmeta by meta_key, add an index:
CREATE INDEX meta_key_index ON wp_postmeta(meta_key);
Clean out old revisions, spam comments, and transient data that accumulates over time. On a mature WordPress site, the wp_options table can grow to thousands of autoloaded rows. Use WP-Optimize or a similar plugin to prune the cruft.
For very high traffic, consider moving from MySQL to MariaDB or enabling query caching at the server level. Check with your host—many have query cache settings you can toggle in cPanel or Plesk.
Where to start
Run a baseline test first. Pick WebPageTest or GTmetrix, test from a distant location on a throttled connection, and screenshot the waterfall. That's your before state.
Fix the mistakes in this order: compress images before upload, configure one caching layer correctly, lazy-load only below-the-fold images, profile database queries. Those four will give you the biggest gains.
Retest after each fix to confirm improvement. If a change doesn't move the needle, roll it back and try the next one. Speed optimization is empirical work—measure, change, measure again.
