⚡ Theme Performance Optimization
Create lightning-fast WordPress themes
Master lazy loading, caching, database optimization, and Core Web Vitals
Learning Objectives
- Understand Core Web Vitals and performance metrics
- Implement lazy loading for images and content
- Optimize CSS and JavaScript delivery
- Master WordPress caching strategies
- Optimize database queries
- Implement critical CSS
- Use CDN and asset optimization
- Monitor and measure performance
Understanding Performance Metrics
Performance directly impacts user experience, SEO rankings, and conversion rates. A 1-second delay in page load time can result in a 7% reduction in conversions.
Core Web Vitals
- Largest Contentful Paint (LCP): Loading performance - should occur within 2.5 seconds
- First Input Delay (FID): Interactivity - pages should have FID less than 100 milliseconds
- Cumulative Layout Shift (CLS): Visual stability - pages should maintain CLS less than 0.1
- Time to First Byte (TTFB): Server response time - should be under 200ms
- First Contentful Paint (FCP): First visual feedback - should occur within 1.8 seconds
Target Performance Metrics
LCP
2.5s
Good < 2.5s
FID
100ms
Good < 100ms
CLS
0.1
Good < 0.1
TTFB
200ms
Good < 200ms
Page Size
2MB
Target < 1MB
Requests
50
Target < 30
Implementing Lazy Loading
Native Lazy Loading
<?php
/**
* Add native lazy loading to images
*/
function add_lazy_loading_attribute( $attributes, $attachment, $size ) {
// Skip if loading attribute already defined
if ( isset( $attributes['loading'] ) ) {
return $attributes;
}
// Add lazy loading
$attributes['loading'] = 'lazy';
// Add decoding async for better performance
$attributes['decoding'] = 'async';
return $attributes;
}
add_filter( 'wp_get_attachment_image_attributes', 'add_lazy_loading_attribute', 10, 3 );
/**
* Lazy load content images
*/
function add_lazy_loading_to_content( $content ) {
// Don't lazy load if is admin
if ( is_admin() ) {
return $content;
}
// Add loading="lazy" to images
$content = preg_replace(
'/(<img(?![^>]*loading=)[^>]*)(\/?)>/i',
'$1 loading="lazy" decoding="async"$2>',
$content
);
// Add loading="lazy" to iframes
$content = preg_replace(
'/(<iframe(?![^>]*loading=)[^>]*)(\/?)>/i',
'$1 loading="lazy"$2>',
$content
);
return $content;
}
add_filter( 'the_content', 'add_lazy_loading_to_content' );
/**
* Advanced lazy loading with Intersection Observer
*/
function enqueue_lazy_loading_script() {
wp_enqueue_script(
'lazy-loading',
get_template_directory_uri() . '/assets/js/lazy-loading.js',
array(),
'1.0.0',
true
);
}
add_action( 'wp_enqueue_scripts', 'enqueue_lazy_loading_script' );
JavaScript Lazy Loading (lazy-loading.js)
// Intersection Observer for lazy loading
document.addEventListener('DOMContentLoaded', function() {
// Images
const lazyImages = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
// Handle srcset
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
// Remove data attributes
delete img.dataset.src;
delete img.dataset.srcset;
// Add loaded class for animations
img.classList.add('loaded');
// Stop observing
observer.unobserve(img);
}
});
}, {
rootMargin: '50px'
});
lazyImages.forEach(img => imageObserver.observe(img));
// Background images
const lazyBackgrounds = document.querySelectorAll('[data-bg]');
const bgObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
element.style.backgroundImage = `url(${element.dataset.bg})`;
delete element.dataset.bg;
element.classList.add('bg-loaded');
observer.unobserve(element);
}
});
});
lazyBackgrounds.forEach(el => bgObserver.observe(el));
});
CSS Optimization
Critical CSS Implementation
<?php
/**
* Inline critical CSS
*/
function inline_critical_css() {
if ( is_front_page() ) {
$critical_css = file_get_contents( get_template_directory() . '/assets/css/critical-home.css' );
} else {
$critical_css = file_get_contents( get_template_directory() . '/assets/css/critical.css' );
}
if ( $critical_css ) {
echo '<style id="critical-css">' . $critical_css . '</style>';
}
}
add_action( 'wp_head', 'inline_critical_css', 5 );
/**
* Defer non-critical CSS
*/
function defer_non_critical_css() {
?>
<link rel="preload" href="<?php echo get_stylesheet_uri(); ?>" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>"></noscript>
<?php
}
add_action( 'wp_head', 'defer_non_critical_css', 20 );
/**
* Remove unused CSS
*/
function remove_unused_css() {
// Remove Gutenberg block CSS if not using blocks
if ( ! is_singular() ) {
wp_dequeue_style( 'wp-block-library' );
wp_dequeue_style( 'wp-block-library-theme' );
}
// Remove emoji styles
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
}
add_action( 'wp_enqueue_scripts', 'remove_unused_css', 100 );
/**
* Minify inline CSS
*/
function minify_css( $css ) {
// Remove comments
$css = preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css );
// Remove whitespace
$css = str_replace( array("\r\n", "\r", "\n", "\t", ' ', ' ', ' '), '', $css );
// Remove unnecessary spaces
$css = preg_replace( '/\s*([{}|:;,])\s+/', '$1', $css );
$css = preg_replace( '/\s\s+(.*)/', '$1', $css );
return $css;
}
JavaScript Optimization
Optimizing Script Loading
<?php
/**
* Defer JavaScript loading
*/
function defer_parsing_of_js( $tag, $handle, $src ) {
// Scripts to defer
$defer = array(
'jquery-migrate',
'comment-reply',
'theme-script'
);
if ( in_array( $handle, $defer ) ) {
return str_replace( ' src', ' defer src', $tag );
}
return $tag;
}
add_filter( 'script_loader_tag', 'defer_parsing_of_js', 10, 3 );
/**
* Async JavaScript loading
*/
function async_scripts( $tag, $handle, $src ) {
// Scripts to load async
$async = array(
'google-analytics',
'facebook-pixel'
);
if ( in_array( $handle, $async ) ) {
return str_replace( ' src', ' async src', $tag );
}
return $tag;
}
add_filter( 'script_loader_tag', 'async_scripts', 10, 3 );
/**
* Move scripts to footer
*/
function move_scripts_to_footer() {
remove_action( 'wp_head', 'wp_print_scripts' );
remove_action( 'wp_head', 'wp_print_head_scripts', 9 );
remove_action( 'wp_head', 'wp_enqueue_scripts', 1 );
add_action( 'wp_footer', 'wp_print_scripts', 5 );
add_action( 'wp_footer', 'wp_print_head_scripts', 5 );
add_action( 'wp_footer', 'wp_enqueue_scripts', 5 );
}
add_action( 'after_setup_theme', 'move_scripts_to_footer' );
/**
* Conditional script loading
*/
function conditional_script_loading() {
// Only load contact form script on contact page
if ( ! is_page( 'contact' ) ) {
wp_dequeue_script( 'contact-form-7' );
wp_dequeue_style( 'contact-form-7' );
}
// Only load slider on homepage
if ( ! is_front_page() ) {
wp_dequeue_script( 'slider-script' );
wp_dequeue_style( 'slider-style' );
}
}
add_action( 'wp_enqueue_scripts', 'conditional_script_loading', 100 );
Database Query Optimization
Efficient Database Queries
<?php
/**
* Optimize WP_Query
*/
function get_optimized_posts() {
$args = array(
'post_type' => 'post',
'posts_per_page' => 10,
'no_found_rows' => true, // Skip pagination count
'update_post_meta_cache' => false, // Skip meta if not needed
'update_post_term_cache' => false, // Skip terms if not needed
'fields' => 'ids', // Get only IDs if that's all you need
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC'
);
return new WP_Query( $args );
}
/**
* Use transients for expensive queries
*/
function get_popular_posts() {
$popular_posts = get_transient( 'popular_posts' );
if ( false === $popular_posts ) {
$args = array(
'post_type' => 'post',
'posts_per_page' => 5,
'meta_key' => 'post_views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC'
);
$popular_posts = new WP_Query( $args );
// Cache for 12 hours
set_transient( 'popular_posts', $popular_posts, 12 * HOUR_IN_SECONDS );
}
return $popular_posts;
}
/**
* Delete expired transients
*/
function delete_expired_transients() {
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP()"
);
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_site_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP()"
);
}
add_action( 'wp_scheduled_delete', 'delete_expired_transients' );
/**
* Optimize autoloaded options
*/
function optimize_autoloaded_options() {
global $wpdb;
// Get size of autoloaded data
$autoload_size = $wpdb->get_var(
"SELECT SUM(LENGTH(option_value))
FROM {$wpdb->options}
WHERE autoload = 'yes'"
);
// If over 1MB, optimize
if ( $autoload_size > 1000000 ) {
// Set large options to not autoload
$wpdb->query(
"UPDATE {$wpdb->options}
SET autoload = 'no'
WHERE autoload = 'yes'
AND LENGTH(option_value) > 5000"
);
}
}
WordPress Caching
Implementing Object Caching
<?php
/**
* Object cache implementation
*/
function get_cached_data( $key ) {
// Try to get from cache
$data = wp_cache_get( $key, 'my_theme' );
if ( false === $data ) {
// Generate data
$data = expensive_function();
// Store in cache for 1 hour
wp_cache_set( $key, $data, 'my_theme', HOUR_IN_SECONDS );
}
return $data;
}
/**
* Fragment caching
*/
function cached_sidebar() {
$cache_key = 'sidebar_' . get_current_blog_id();
$sidebar = get_transient( $cache_key );
if ( false === $sidebar ) {
ob_start();
dynamic_sidebar( 'sidebar-1' );
$sidebar = ob_get_clean();
set_transient( $cache_key, $sidebar, HOUR_IN_SECONDS );
}
echo $sidebar;
}
/**
* Page cache headers
*/
function set_cache_headers() {
if ( ! is_user_logged_in() && ! is_admin() ) {
header( 'Cache-Control: public, max-age=3600' );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 3600 ) . ' GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', get_the_modified_time( 'U' ) ) . ' GMT' );
header( 'Vary: Accept-Encoding' );
}
}
add_action( 'send_headers', 'set_cache_headers' );
/**
* Clear cache on post update
*/
function clear_cache_on_save( $post_id ) {
// Clear transients
delete_transient( 'popular_posts' );
delete_transient( 'recent_posts' );
delete_transient( 'sidebar_' . get_current_blog_id() );
// Clear object cache
wp_cache_delete( 'homepage_data', 'my_theme' );
// Trigger cache purge for caching plugins
if ( function_exists( 'wp_cache_clear_cache' ) ) {
wp_cache_clear_cache();
}
}
add_action( 'save_post', 'clear_cache_on_save' );
Image Optimization
Advanced Image Optimization
<?php
/**
* Responsive images with srcset
*/
function responsive_image( $attachment_id, $size = 'full', $attr = array() ) {
$image = wp_get_attachment_image_src( $attachment_id, $size );
if ( ! $image ) {
return '';
}
$srcset = wp_get_attachment_image_srcset( $attachment_id, $size );
$sizes = wp_get_attachment_image_sizes( $attachment_id, $size );
$attr = wp_parse_args( $attr, array(
'src' => $image[0],
'srcset' => $srcset,
'sizes' => $sizes,
'loading' => 'lazy',
'decoding' => 'async'
) );
$html = '<img';
foreach ( $attr as $name => $value ) {
$html .= ' ' . $name . '="' . esc_attr( $value ) . '"';
}
$html .= '>';
return $html;
}
/**
* WebP support
*/
function add_webp_support( $mimes ) {
$mimes['webp'] = 'image/webp';
return $mimes;
}
add_filter( 'upload_mimes', 'add_webp_support' );
/**
* Serve WebP images when supported
*/
function serve_webp_images( $image_url ) {
// Check if browser supports WebP
if ( isset( $_SERVER['HTTP_ACCEPT'] ) &&
strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false ) {
$webp_url = str_replace(
array( '.jpg', '.jpeg', '.png' ),
'.webp',
$image_url
);
// Check if WebP version exists
$upload_dir = wp_upload_dir();
$webp_path = str_replace(
$upload_dir['baseurl'],
$upload_dir['basedir'],
$webp_url
);
if ( file_exists( $webp_path ) ) {
return $webp_url;
}
}
return $image_url;
}
add_filter( 'wp_get_attachment_url', 'serve_webp_images' );
/**
* Optimize image sizes
*/
function optimize_image_sizes() {
// Remove default image sizes
remove_image_size( '1536x1536' );
remove_image_size( '2048x2048' );
// Add optimized sizes
add_image_size( 'hero', 1920, 800, true );
add_image_size( 'card', 400, 300, true );
add_image_size( 'thumbnail-retina', 300, 300, true );
}
add_action( 'after_setup_theme', 'optimize_image_sizes' );
/**
* JPEG quality
*/
add_filter( 'jpeg_quality', function() {
return 85;
});
Resource Hints
Preloading Critical Resources
<?php
/**
* Add resource hints
*/
function add_resource_hints() {
// Preload fonts
echo '<link rel="preload" as="font" type="font/woff2" crossorigin
href="' . get_template_directory_uri() . '/assets/fonts/main.woff2">';
// Preload critical CSS
echo '<link rel="preload" as="style"
href="' . get_template_directory_uri() . '/assets/css/critical.css">';
// Preconnect to external domains
echo '<link rel="preconnect" href="https://fonts.googleapis.com">';
echo '<link rel="dns-prefetch" href="https://fonts.googleapis.com">';
// Prefetch next page (for pagination)
if ( is_single() ) {
$next_post = get_next_post();
if ( $next_post ) {
echo '<link rel="prefetch" href="' . get_permalink( $next_post ) . '">';
}
}
}
add_action( 'wp_head', 'add_resource_hints', 2 );
/**
* Preload above-the-fold images
*/
function preload_hero_image() {
if ( has_post_thumbnail() ) {
$image_id = get_post_thumbnail_id();
$image_url = wp_get_attachment_image_url( $image_id, 'hero' );
if ( $image_url ) {
echo '<link rel="preload" as="image" href="' . esc_url( $image_url ) . '">';
}
}
}
add_action( 'wp_head', 'preload_hero_image', 3 );
Optimization Strategies
Minimize HTTP Requests HIGH
- Combine CSS files
- Combine JavaScript files
- Use CSS sprites
- Inline small CSS/JS
- Remove unnecessary plugins
Optimize Images HIGH
- Use WebP format
- Implement lazy loading
- Serve responsive images
- Compress images
- Use CDN for images
Leverage Caching HIGH
- Browser caching
- Page caching
- Object caching
- Database caching
- CDN caching
Optimize Database MEDIUM
- Clean post revisions
- Optimize tables
- Remove spam comments
- Delete transients
- Limit autoloaded data
Minify Resources MEDIUM
- Minify CSS
- Minify JavaScript
- Minify HTML
- Remove comments
- Remove whitespace
Server Optimization HIGH
- Use PHP 8+
- Enable Gzip compression
- Use HTTP/2
- Optimize server config
- Use SSD hosting
Performance Monitoring
Custom Performance Monitoring
<?php
/**
* Track page load time
*/
function track_page_load_time() {
?>
<script>
window.addEventListener('load', function() {
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
const connectTime = perfData.responseEnd - perfData.requestStart;
const renderTime = perfData.domComplete - perfData.domLoading;
console.log('Page Load Time:', pageLoadTime + 'ms');
console.log('Connect Time:', connectTime + 'ms');
console.log('Render Time:', renderTime + 'ms');
// Send to analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'timing_complete', {
'name': 'load',
'value': pageLoadTime,
'event_category': 'Performance'
});
}
});
</script>
<?php
}
add_action( 'wp_footer', 'track_page_load_time' );
/**
* Monitor database queries
*/
function monitor_db_queries() {
if ( current_user_can( 'manage_options' ) && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
global $wpdb;
echo '<!-- Queries: ' . $wpdb->num_queries . ' -->';
echo '<!-- Timer: ' . timer_stop( 0 ) . ' seconds -->';
}
}
add_action( 'wp_footer', 'monitor_db_queries', 100 );
Performance Optimization Checklist
- Enable lazy loading for images and iframes
- Implement critical CSS inline
- Defer non-critical CSS
- Defer or async JavaScript files
- Minify CSS, JavaScript, and HTML
- Enable Gzip compression
- Optimize images (WebP, compression)
- Use responsive images with srcset
- Implement browser caching
- Use a CDN for static assets
- Optimize database queries
- Use transients for expensive operations
- Remove unused CSS and JavaScript
- Limit post revisions
- Optimize autoloaded options
- Preload critical resources
- Use HTTP/2
- Enable page caching
- Monitor Core Web Vitals
- Regular performance testing
Best Practices
Performance Best Practices
- Mobile-first: Optimize for mobile devices first
- Progressive enhancement: Start with core functionality
- Measure regularly: Monitor performance metrics
- Budget performance: Set performance budgets
- Optimize critical path: Prioritize above-the-fold content
- Use modern formats: WebP, AVIF for images
- Avoid render-blocking: Eliminate render-blocking resources
- Reduce JavaScript: Ship less JavaScript
- Cache aggressively: Cache everything possible
- Test on real devices: Don't rely only on tools
Don't over-optimize at the expense of maintainability. Balance performance improvements with code clarity and development speed.
Use tools like Lighthouse, GTmetrix, and WebPageTest regularly to monitor your theme's performance and identify areas for improvement.
Practice Exercise
Optimize Your Theme Performance