Skip to main content

Course Progress

Loading...

⚡ 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

Implement comprehensive performance optimizations:

  1. Implement lazy loading for all images
  2. Create and inline critical CSS
  3. Defer non-critical CSS and JavaScript
  4. Minify all CSS and JavaScript files
  5. Optimize and compress images
  6. Implement caching strategy
  7. Add resource hints (preload, prefetch)
  8. Optimize database queries
  9. Test with PageSpeed Insights
  10. Achieve 90+ performance score

Additional Resources