A slow page costs you visitors. Every extra second of load time pushes users toward the back button, and search engines notice. I've seen sites drop half their traffic because nobody fixed the basics.
The good news? You don't need a PhD in web performance to make a difference. Seven straightforward techniques will get you most of the way there, and you can implement all of them in an afternoon. Let's walk through what actually moves the needle.
Compress your images properly
Images are the biggest files on most pages. A single uncompressed photo can weigh more than your entire HTML, CSS, and JavaScript combined.
Start by choosing the right format. JPEG works for photographs. PNG handles graphics with transparency. WebP beats both on file size while maintaining quality, and browser support is now nearly universal—IE11 is the only major holdout, and even Microsoft killed that in 2022.
For photos, aim for 80-85% quality on the JPEG scale. The human eye won't spot the difference, but your bandwidth bill will. Tools like ImageMagick let you batch-process entire directories:
for img in *.jpg; do
convert "$img" -quality 85 -strip "optimized_$img"
done
The -strip flag removes EXIF metadata you don't need on the web. GPS coordinates and camera settings just add weight.
WebP conversion is similar:
cwebp -q 85 input.jpg -o output.webp
Serve WebP to browsers that support it and fall back to JPEG for the rest. The <picture> element handles this:
<picture>
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image">
</picture>
WordPress plugins like ShortPixel or Imagify will automate the whole process if you'd rather not touch the command line. They create WebP versions on upload and insert the picture tags for you.
Don't forget to resize images to their display dimensions. A 4000px wide image scaled down to 800px in CSS still downloads at full size. Create multiple versions and use srcset to serve the right one:
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, 800px"
alt="Description">
Set up a CDN the right way
A content delivery network caches your static files on servers close to your visitors. Someone in Sydney gets assets from Sydney instead of waiting for a round trip to Virginia.
Cloudflare is the obvious first choice because it's free and takes five minutes to configure. Point your domain's nameservers to Cloudflare, wait for DNS propagation, and enable the CDN from the dashboard. That's it for basic setup.
But most people stop there and miss half the benefit. Check your caching rules. By default, Cloudflare only caches common static file types—images, CSS, JavaScript. If you're serving fonts, PDFs, or other assets, you need a Page Rule to cache them too:
- Go to Rules > Page Rules
- Create a rule for your assets directory (e.g.,
yoursite.com/assets/*) - Set Cache Level to "Cache Everything"
- Set Edge Cache TTL to one month or more
For WordPress sites, most of your traffic hits PHP files that generate dynamic HTML. Cloudflare won't cache those by default, and that's usually correct—you don't want logged-in users seeing cached versions meant for guests. But if you're running a blog or marketing site with no personalization, you can cache HTML too.
Install the Cloudflare WordPress plugin and enable automatic platform optimization. It caches HTML at the edge while clearing the cache when you publish or update content. In support tickets I handled, this single change often cut server load by 60-70%.
Other CDN options like KeyCDN, BunnyCDN, or StackPath work well if you need more control or want to avoid putting your DNS eggs in one basket. The setup is similar: you get a CDN URL, configure your site to load static assets from that URL, and the CDN pulls files from your origin server on first request.
Minify and combine CSS and JavaScript
Whitespace, comments, and long variable names make code readable. They also make files bigger. Minification strips everything that isn't functionally necessary.
For CSS:
cleancss -o style.min.css style.css
For JavaScript:
terser script.js -o script.min.js
Build tools like Webpack, Gulp, or Parcel handle this automatically as part of your compilation step. If you're not using a build process, online tools work fine—just remember to minify before you deploy, not in production.
Combining files is trickier. Fewer HTTP requests used to mean faster load times, so everyone concatenated all their CSS into one file and all their JavaScript into another. But HTTP/2 changed that math. It multiplexes requests over a single connection, so downloading ten small files costs about the same as one big one.
Still, combining can help if you have dozens of tiny files—each file has overhead even with HTTP/2. The sweet spot is usually 3-5 combined files, grouped by page or feature. Critical above-the-fold CSS goes in one file, below-the-fold styles in another, and third-party scripts in a third.
WordPress users should install Autoptimize or WP Rocket. Both minify and combine files with a few checkbox clicks, and they're smart enough not to break things most of the time. Test thoroughly after enabling, especially if you have a lot of plugins.
Enable browser caching with the right headers
Every time someone visits your site, their browser asks your server: "Do I need to re-download this file?" You want the answer to be "no" as often as possible.
Cache-Control headers tell browsers how long to keep files. Set long expiration times for assets that rarely change:
# Apache .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
</IfModule>
For Nginx:
location ~* \.(jpg|jpeg|png|webp|css|js|pdf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
The immutable directive tells browsers that the file will never change, so they shouldn't even revalidate it until the expiration date. Use this for versioned assets like style.v2.css or app.abc123.js.
HTML files are different. You want browsers to check for updates every time, but you also want them to validate efficiently. Set a short max-age and use ETags:
location = /index.html {
expires 0;
add_header Cache-Control "no-cache, must-revalidate";
}
The no-cache directive doesn't mean "don't cache"—it means "validate with the server before using the cached copy." If the ETag matches, the server sends a 304 Not Modified response with no body, saving bandwidth.
Implement lazy loading for images and iframes
Why download images that users might never see? Lazy loading defers offscreen images until the user scrolls near them.
Browsers now do this natively:
<img src="photo.jpg" loading="lazy" alt="Description">
That one attribute tells the browser to delay loading until the image is about to enter the viewport. It works for iframes too:
<iframe src="video-embed.html" loading="lazy"></iframe>
Support is excellent—Chrome, Edge, Firefox, and Safari all implement it. For older browsers, the attribute is safely ignored and images load normally, so there's no downside to adding it everywhere.
Don't lazy-load anything above the fold. The first screen of content should load immediately, or you'll hurt perceived performance more than you help actual performance. Only apply loading="lazy" to images that start offscreen.
If you need more control—like loading images two screens ahead instead of one—JavaScript libraries like lozad.js or lazysizes give you that flexibility. But start with the native attribute. It's simpler and faster.
Use compression for text files
Gzip or Brotli compression can shrink HTML, CSS, and JavaScript by 70-80%. Most web servers support at least Gzip, and configuration takes two minutes.
Apache:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
</IfModule>
Nginx:
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
Brotli offers better compression than Gzip—sometimes 15-20% smaller—but requires a newer Nginx version or an Apache module. If you're on a managed host, check whether it's available. If so, enable both and let the browser pick:
brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml;
Don't compress images, videos, or PDFs. They're already compressed, and attempting to compress them again wastes CPU cycles for no benefit.
Optimize your database queries
Slow queries kill performance on dynamic sites. A page that generates in 50ms on your dev machine might take three seconds in production when the database has a million rows.
WordPress is the usual suspect here. Every plugin adds queries, and a badly written one can tank your whole site. Install Query Monitor to see what's happening:
wp plugin install query-monitor --activate
Load a few pages and check the Query Monitor panel. Look for:
- Duplicate queries (usually fixable with caching)
- Queries without indexes (shows up as "Using filesort" or "Using temporary" in EXPLAIN)
- Queries that return thousands of rows when they only need ten
Add indexes to columns you filter or sort by:
ALTER TABLE wp_postmeta ADD INDEX meta_key_value (meta_key, meta_value(191));
The (191) limit is necessary for utf8mb4 columns in MySQL to stay under the index length limit. Skip it for other column types.
Object caching helps too. Redis or Memcached sits between your application and the database, storing query results in memory. The first request hits the database, but subsequent requests pull from cache until it expires.
Install Redis on your server:
apt install redis-server php-redis
systemctl enable redis-server
systemctl start redis-server
Then install the Redis Object Cache plugin for WordPress. It hooks into WordPress's caching layer automatically.
For non-WordPress applications, the principle is the same: identify slow queries, add indexes, and cache what you can.
What works best together?
You'll get the biggest gains by stacking techniques. Image compression + CDN + lazy loading handles your largest files. Minification + caching headers + compression shrinks and caches everything else. Database optimization keeps the backend fast.
Start with the low-hanging fruit—image compression and caching headers take minutes to implement and often cut load times in half. Then move to the techniques that require more setup, like CDN configuration and database optimization.
Test after every change. Tools like WebPageTest or GTmetrix show you exactly what's slow and what's fast. A change that helps one site might hurt another, depending on your traffic patterns and content mix.
Common questions
Do I need all seven techniques?
No, but you'll probably need most of them. Image compression and caching headers help everyone. The others depend on your site—if you don't have many images below the fold, lazy loading won't help much. If your database is already fast, optimizing queries won't move the needle.
How much faster will my site get?
It depends on where you start. A site that's doing nothing right can often cut load times by 60-80% just from image compression and caching. A site that's already doing some optimization might see 20-30% improvement from finishing the job.
Will a CDN help if all my traffic is local?
Yes, but less than it helps international sites. Even local visitors benefit from cached assets and parallel connections. The CDN's edge servers also absorb traffic spikes that might overwhelm your origin server.
Can I break my site by minifying JavaScript?
Yes, if your code relies on specific formatting or comments that contain instructions for other tools. That's rare, but it happens. Always test minified code in staging before pushing to production, and keep the original files in version control so you can roll back.
Focus on what slows you down most
Page speed isn't about perfection. It's about fixing the bottlenecks that actually hurt your users.
Run a speed test, look at the waterfall chart, and find the fattest bars. Those are your targets. An 8MB image? Compress it. Twelve seconds of server processing? Check your queries. 400ms of render-blocking CSS? Inline the critical stuff and defer the rest.
The seven techniques in this article handle the most common problems. Pick the ones that match your bottlenecks, implement them, measure the results, and move on to the next task. Speed is important, but at some point you're optimizing tenths of a second while your users need new features or better content.
Get fast enough, then ship.
