⚡ Asset Optimization and Best Practices
Maximize WordPress theme performance
Learn minification, compression, lazy loading, and advanced optimization techniques
Learning Objectives
- Understand performance metrics and goals
- Implement asset minification and compression
- Master lazy loading techniques
- Optimize critical rendering path
- Implement browser caching strategies
- Use CDNs effectively
- Optimize images and media
- Apply performance best practices
Why Asset Optimization Matters
Website performance directly impacts user experience, SEO rankings, and conversion rates. Optimizing assets in WordPress themes is crucial for creating fast, efficient websites.
First Contentful Paint
< 1.8s
Good performance target
Largest Contentful Paint
< 2.5s
Main content visible
Time to Interactive
< 3.8s
Page fully interactive
Cumulative Layout Shift
< 0.1
Visual stability
Key Insight
Minification and Compression
Asset Minification
WordPress Minification Setup
<?php
// functions.php
class ThemeAssetOptimizer {
private $minify_css = true;
private $minify_js = true;
private $combine_files = true;
public function __construct() {
add_action('wp_enqueue_scripts', array($this, 'optimize_assets'), 999);
add_filter('script_loader_tag', array($this, 'add_async_defer'), 10, 3);
add_filter('style_loader_tag', array($this, 'optimize_css_loading'), 10, 4);
}
public function optimize_assets() {
if (!is_admin() && !is_customize_preview()) {
// Remove version strings
add_filter('script_loader_src', array($this, 'remove_version_strings'), 15, 1);
add_filter('style_loader_src', array($this, 'remove_version_strings'), 15, 1);
// Conditionally load assets
$this->conditional_loading();
// Load minified versions in production
if (!WP_DEBUG) {
$this->load_minified_assets();
}
}
}
private function load_minified_assets() {
// Dequeue original and enqueue minified
if (wp_style_is('theme-style', 'enqueued')) {
wp_dequeue_style('theme-style');
wp_enqueue_style(
'theme-style-min',
get_template_directory_uri() . '/assets/css/style.min.css',
array(),
filemtime(get_template_directory() . '/assets/css/style.min.css')
);
}
if (wp_script_is('theme-script', 'enqueued')) {
wp_dequeue_script('theme-script');
wp_enqueue_script(
'theme-script-min',
get_template_directory_uri() . '/assets/js/main.min.js',
array(),
filemtime(get_template_directory() . '/assets/js/main.min.js'),
true
);
}
}
private function conditional_loading() {
// Only load contact form scripts on contact page
if (!is_page('contact')) {
wp_dequeue_script('contact-form-7');
wp_dequeue_style('contact-form-7');
}
// Only load comment scripts on single posts with comments
if (!is_singular() || !comments_open()) {
wp_dequeue_script('comment-reply');
}
// Remove block library CSS if not using Gutenberg
if (!has_blocks()) {
wp_dequeue_style('wp-block-library');
wp_dequeue_style('wp-block-library-theme');
}
}
public function add_async_defer($tag, $handle, $src) {
// Async loading for non-critical scripts
$async_scripts = array('google-analytics', 'facebook-pixel');
if (in_array($handle, $async_scripts)) {
return str_replace(' src', ' async src', $tag);
}
// Defer loading for other scripts
$defer_scripts = array('theme-script-min', 'animations');
if (in_array($handle, $defer_scripts)) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}
public function optimize_css_loading($html, $handle, $href, $media) {
// Preload critical CSS
$critical_styles = array('theme-critical', 'above-fold');
if (in_array($handle, $critical_styles)) {
$html = sprintf(
'<link rel="preload" as="style" href="%s" onload="this.onload=null;this.rel=\'stylesheet\'">
<noscript>%s</noscript>',
$href,
$html
);
}
return $html;
}
public function remove_version_strings($src) {
if (strpos($src, 'ver=')) {
$src = remove_query_arg('ver', $src);
}
return $src;
}
}
// Initialize optimizer
new ThemeAssetOptimizer();
Gulp Task for Minification
// gulpfile.js
const gulp = require('gulp');
const uglify = require('gulp-uglify');
const cleanCSS = require('gulp-clean-css');
const concat = require('gulp-concat');
const sourcemaps = require('gulp-sourcemaps');
const imagemin = require('gulp-imagemin');
const gzip = require('gulp-gzip');
// Minify JavaScript
gulp.task('minify-js', () => {
return gulp.src('assets/js/*.js')
.pipe(sourcemaps.init())
.pipe(concat('main.min.js'))
.pipe(uglify({
compress: {
drop_console: true,
drop_debugger: true
}
}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('dist/js'))
.pipe(gzip())
.pipe(gulp.dest('dist/js'));
});
// Minify CSS
gulp.task('minify-css', () => {
return gulp.src('assets/css/*.css')
.pipe(sourcemaps.init())
.pipe(concat('style.min.css'))
.pipe(cleanCSS({
level: {
1: {
specialComments: 0
},
2: {
mergeMedia: true,
removeEmpty: true,
removeDuplicates: true
}
}
}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('dist/css'))
.pipe(gzip())
.pipe(gulp.dest('dist/css'));
});
// Optimize images
gulp.task('optimize-images', () => {
return gulp.src('assets/images/**/*')
.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.mozjpeg({quality: 75, progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]))
.pipe(gulp.dest('dist/images'));
});
// Watch task
gulp.task('watch', () => {
gulp.watch('assets/js/*.js', gulp.series('minify-js'));
gulp.watch('assets/css/*.css', gulp.series('minify-css'));
gulp.watch('assets/images/**/*', gulp.series('optimize-images'));
});
// Default task
gulp.task('default', gulp.parallel('minify-js', 'minify-css', 'optimize-images'));
Implementing Lazy Loading
Native Lazy Loading
<?php
// functions.php
// Add lazy loading to images
function mytheme_add_lazy_loading($content) {
// Add loading="lazy" to images
$content = preg_replace(
'/<img(.*?)src=/i',
'<img$1loading="lazy" src=',
$content
);
// Add loading="lazy" to iframes
$content = preg_replace(
'/<iframe(.*?)src=/i',
'<iframe$1loading="lazy" src=',
$content
);
return $content;
}
add_filter('the_content', 'mytheme_add_lazy_loading');
// Lazy load post thumbnails
function mytheme_lazy_load_thumbnails($attr, $attachment, $size) {
$attr['loading'] = 'lazy';
// Add data attributes for advanced lazy loading
$attr['data-src'] = $attr['src'];
$attr['src'] = 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 1 1\'%3E%3C/svg%3E';
$attr['class'] = isset($attr['class']) ? $attr['class'] . ' lazyload' : 'lazyload';
return $attr;
}
add_filter('wp_get_attachment_image_attributes', 'mytheme_lazy_load_thumbnails', 10, 3);
JavaScript Lazy Loading with Intersection Observer
// lazy-load.js
class LazyLoader {
constructor() {
this.images = document.querySelectorAll('img[data-src]');
this.videos = document.querySelectorAll('video[data-src]');
this.iframes = document.querySelectorAll('iframe[data-src]');
this.imageOptions = {
threshold: 0.01,
rootMargin: '50px'
};
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.createObserver();
} else {
// Fallback for older browsers
this.loadAllImages();
}
}
createObserver() {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadElement(entry.target);
observer.unobserve(entry.target);
}
});
}, this.imageOptions);
// Observe all lazy elements
this.images.forEach(img => imageObserver.observe(img));
this.videos.forEach(video => imageObserver.observe(video));
this.iframes.forEach(iframe => imageObserver.observe(iframe));
}
loadElement(element) {
const src = element.dataset.src;
const srcset = element.dataset.srcset;
if (!src) return;
// Handle different element types
if (element.tagName === 'IMG') {
this.loadImage(element, src, srcset);
} else if (element.tagName === 'VIDEO') {
this.loadVideo(element, src);
} else if (element.tagName === 'IFRAME') {
this.loadIframe(element, src);
}
}
loadImage(img, src, srcset) {
// Preload image
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
if (srcset) {
img.srcset = srcset;
}
img.classList.add('loaded');
img.classList.remove('lazyload');
};
tempImg.src = src;
if (srcset) {
tempImg.srcset = srcset;
}
}
loadVideo(video, src) {
video.src = src;
video.load();
video.classList.add('loaded');
}
loadIframe(iframe, src) {
iframe.src = src;
iframe.classList.add('loaded');
}
loadAllImages() {
// Fallback: load all images immediately
this.images.forEach(img => {
if (img.dataset.src) {
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
}
});
}
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => {
new LazyLoader();
});
// Reinitialize after AJAX content loads
document.addEventListener('ajax-content-loaded', () => {
new LazyLoader();
});
Background Image Lazy Loading
// Lazy load background images
class BackgroundLazyLoader {
constructor() {
this.elements = document.querySelectorAll('[data-bg]');
this.init();
}
init() {
if ('IntersectionObserver' in window) {
const bgObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
const bg = element.dataset.bg;
element.style.backgroundImage = `url(${bg})`;
element.classList.add('bg-loaded');
bgObserver.unobserve(element);
}
});
});
this.elements.forEach(el => bgObserver.observe(el));
} else {
// Immediate load for unsupported browsers
this.elements.forEach(el => {
el.style.backgroundImage = `url(${el.dataset.bg})`;
});
}
}
}
new BackgroundLazyLoader();
Critical CSS and Above-the-Fold Optimization
Inline Critical CSS
<?php
// functions.php
function mytheme_inline_critical_css() {
$critical_css_file = get_template_directory() . '/assets/css/critical.min.css';
if (file_exists($critical_css_file)) {
$critical_css = file_get_contents($critical_css_file);
echo '<style id="critical-css">' . $critical_css . '</style>';
}
}
add_action('wp_head', 'mytheme_inline_critical_css', 5);
// Load main CSS asynchronously
function mytheme_async_styles() {
?>
<script>
// Async CSS loading
function loadCSS(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = 'print';
link.onload = function() { this.media = 'all'; };
document.head.appendChild(link);
}
// Load non-critical CSS
loadCSS('<?php echo get_template_directory_uri(); ?>/assets/css/style.min.css');
</script>
<noscript>
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/assets/css/style.min.css">
</noscript>
<?php
}
add_action('wp_head', 'mytheme_async_styles', 100);
Generate Critical CSS with Node
// generate-critical.js
const critical = require('critical');
critical.generate({
base: './',
src: 'index.html',
css: ['assets/css/style.css'],
width: 1300,
height: 900,
target: {
css: 'assets/css/critical.min.css',
uncritical: 'assets/css/uncritical.min.css'
},
minify: true,
extract: true,
ignore: {
atrule: ['@font-face'],
decl: (node, value) => /url\(/.test(value)
}
});
Resource Hints
<?php
// Add resource hints for faster loading
function mytheme_resource_hints($hints, $relation_type) {
// DNS Prefetch for external domains
if ('dns-prefetch' === $relation_type) {
$hints[] = '//fonts.googleapis.com';
$hints[] = '//fonts.gstatic.com';
$hints[] = '//cdnjs.cloudflare.com';
$hints[] = '//www.google-analytics.com';
}
// Preconnect for critical resources
if ('preconnect' === $relation_type) {
$hints[] = 'https://fonts.googleapis.com';
$hints[] = 'https://fonts.gstatic.com';
}
return $hints;
}
add_filter('wp_resource_hints', 'mytheme_resource_hints', 10, 2);
// Preload critical resources
function mytheme_preload_resources() {
// Preload fonts
echo '<link rel="preload" as="font" type="font/woff2" crossorigin href="' .
get_template_directory_uri() . '/assets/fonts/main-font.woff2">';
// Preload hero image
if (is_front_page()) {
$hero_image = get_theme_mod('hero_image');
if ($hero_image) {
echo '<link rel="preload" as="image" href="' . esc_url($hero_image) . '">';
}
}
// Prefetch next page
if (is_single()) {
$next_post = get_next_post();
if ($next_post) {
echo '<link rel="prefetch" href="' . get_permalink($next_post) . '">';
}
}
}
add_action('wp_head', 'mytheme_preload_resources', 2);
Browser Caching and Service Workers
.htaccess Caching Rules
# Browser Caching
<IfModule mod_expires.c>
ExpiresActive On
# Images
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
# CSS and JavaScript
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
# Fonts
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
# HTML
ExpiresByType text/html "access plus 0 seconds"
# Default
ExpiresDefault "access plus 2 days"
</IfModule>
# Cache-Control Headers
<IfModule mod_headers.c>
# Images
<FilesMatch "\.(jpg|jpeg|png|gif|svg|webp|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# CSS and JS
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
# Fonts
<FilesMatch "\.(woff|woff2|ttf|otf|eot)$">
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
# Disable caching for dynamic content
<FilesMatch "\.(php|html?)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>
</IfModule>
# Enable Gzip Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
Service Worker for Offline Caching
// service-worker.js
const CACHE_NAME = 'mytheme-v1.0.0';
const urlsToCache = [
'/',
'/wp-content/themes/mytheme/assets/css/critical.min.css',
'/wp-content/themes/mytheme/assets/js/main.min.js',
'/wp-content/themes/mytheme/assets/fonts/main-font.woff2',
'/offline.html'
];
// Install event
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// Fetch event
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Clone the request
const fetchRequest = event.request.clone();
return fetch(fetchRequest).then(response => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone the response
const responseToCache = response.clone();
// Cache the response
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
.catch(() => {
// Return offline page for navigation requests
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
})
);
});
// Activate event
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
Register Service Worker
<?php
// Register service worker
function mytheme_register_service_worker() {
?>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('<?php echo get_template_directory_uri(); ?>/service-worker.js')
.then(function(registration) {
console.log('ServiceWorker registration successful');
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
</script>
<?php
}
add_action('wp_footer', 'mytheme_register_service_worker');
Image and Media Optimization
Responsive Images with srcset
<?php
// Generate responsive images
function mytheme_responsive_image($attachment_id, $sizes = array()) {
$default_sizes = array(
'(max-width: 480px) 480px',
'(max-width: 768px) 768px',
'(max-width: 1024px) 1024px',
'1920px'
);
$sizes = !empty($sizes) ? $sizes : $default_sizes;
$img_src = wp_get_attachment_image_src($attachment_id, 'full');
$img_srcset = wp_get_attachment_image_srcset($attachment_id, 'full');
$img_sizes = implode(', ', $sizes);
$output = sprintf(
'<img src="%s" srcset="%s" sizes="%s" loading="lazy" alt="%s">',
esc_url($img_src[0]),
esc_attr($img_srcset),
esc_attr($img_sizes),
esc_attr(get_post_meta($attachment_id, '_wp_attachment_image_alt', true))
);
return $output;
}
// Add WebP support
function mytheme_webp_support($mimes) {
$mimes['webp'] = 'image/webp';
return $mimes;
}
add_filter('mime_types', 'mytheme_webp_support');
// Serve WebP images when available
function mytheme_serve_webp($image_url) {
$image_path = str_replace(site_url(), ABSPATH, $image_url);
$webp_path = preg_replace('/\.(jpg|jpeg|png)$/i', '.webp', $image_path);
$webp_url = preg_replace('/\.(jpg|jpeg|png)$/i', '.webp', $image_url);
if (file_exists($webp_path) && mytheme_browser_supports_webp()) {
return $webp_url;
}
return $image_url;
}
function mytheme_browser_supports_webp() {
return isset($_SERVER['HTTP_ACCEPT']) &&
strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false;
}
Optimize Upload Images
<?php
// Automatically optimize uploaded images
function mytheme_optimize_uploaded_image($metadata, $attachment_id) {
$uploads_dir = wp_upload_dir();
$file_path = $uploads_dir['basedir'] . '/' . $metadata['file'];
// Get image type
$image_type = wp_check_filetype($file_path);
switch ($image_type['type']) {
case 'image/jpeg':
case 'image/jpg':
$image = imagecreatefromjpeg($file_path);
imagejpeg($image, $file_path, 85); // 85% quality
break;
case 'image/png':
$image = imagecreatefrompng($file_path);
imagepng($image, $file_path, 8); // Compression level 8
break;
}
if (isset($image)) {
imagedestroy($image);
}
// Optimize thumbnails
if (isset($metadata['sizes'])) {
foreach ($metadata['sizes'] as $size => $data) {
$thumb_path = $uploads_dir['basedir'] . '/' .
dirname($metadata['file']) . '/' . $data['file'];
// Apply same optimization to thumbnails
if (file_exists($thumb_path)) {
// Optimization code here
}
}
}
return $metadata;
}
add_filter('wp_generate_attachment_metadata', 'mytheme_optimize_uploaded_image', 10, 2);
Performance Monitoring
Performance Monitoring Script
// performance-monitor.js
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.init();
}
init() {
if ('performance' in window && 'PerformanceObserver' in window) {
this.observePerformance();
this.measureMetrics();
}
}
observePerformance() {
// Observe Largest Contentful Paint
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;
console.log('LCP:', this.metrics.lcp);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// Observe First Input Delay
const fidObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.metrics.fid = entry.processingStart - entry.startTime;
console.log('FID:', this.metrics.fid);
});
});
fidObserver.observe({ entryTypes: ['first-input'] });
// Observe Cumulative Layout Shift
let clsValue = 0;
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
this.metrics.cls = clsValue;
console.log('CLS:', this.metrics.cls);
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
}
measureMetrics() {
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
// Calculate metrics
this.metrics.domContentLoaded = perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart;
this.metrics.loadComplete = perfData.loadEventEnd - perfData.loadEventStart;
this.metrics.domInteractive = perfData.domInteractive;
this.metrics.firstByte = perfData.responseStart - perfData.requestStart;
// Send to analytics
this.sendMetrics();
});
}
sendMetrics() {
// Send to Google Analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'performance', {
'event_category': 'Web Vitals',
'event_label': 'Page Load',
'value': Math.round(this.metrics.lcp),
'metric_lcp': this.metrics.lcp,
'metric_fid': this.metrics.fid,
'metric_cls': this.metrics.cls
});
}
// Or send to custom endpoint
fetch('/wp-json/mytheme/v1/performance', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(this.metrics)
});
}
}
// Initialize
new PerformanceMonitor();
Complete Optimization Checklist
Pre-Launch Optimization Checklist
- Minify all CSS and JavaScript files
- Combine and bundle assets where appropriate
- Implement lazy loading for images and videos
- Generate and inline critical CSS
- Add resource hints (dns-prefetch, preconnect, preload)
- Optimize and compress all images
- Provide WebP alternatives for images
- Configure browser caching headers
- Enable Gzip/Brotli compression
- Remove unused CSS and JavaScript
- Defer non-critical JavaScript
- Optimize web fonts loading
- Implement service worker for offline support
- Remove query strings from static resources
- Optimize database queries
- Use CDN for static assets
- Test with PageSpeed Insights
- Monitor Core Web Vitals
- Implement performance budgets
- Document optimization strategies
Best Practices
Asset Optimization Best Practices
- Mobile-first approach: Optimize for mobile devices first
- Progressive enhancement: Start with basic functionality
- Performance budgets: Set limits for page weight and load time
- Regular audits: Use Lighthouse and WebPageTest regularly
- Monitor real users: Track Core Web Vitals in production
- Optimize critical path: Prioritize above-the-fold content
- Use modern formats: WebP, AVIF for images, WOFF2 for fonts
- Eliminate render-blocking: Async/defer scripts, critical CSS
- Cache aggressively: Use long cache lifetimes with versioning
- Test thoroughly: Check performance across devices and networks
Over-optimization can lead to maintainability issues. Find the right balance between performance and code complexity. Always measure the impact of optimizations.
Use Chrome DevTools' Coverage tab to identify unused CSS and JavaScript. This can help you eliminate dead code and reduce bundle sizes.
Practice Exercise
Optimize Your Theme