Wordpress Performance Optimization
Wordpress Performance Optimization
Website performance is crucial for user experience, SEO rankings, and conversion rates. Studies
show that a 1-second delay in page load time can result in a 7% reduction in conversions.
WordPress sites, while powerful and flexible, require careful optimization to achieve optimal
performance.
Time to First Byte (TTFB)
: Time from request to first byte received (target: <600ms)
First Contentful Paint (FCP)
: Time until first content is visible (target: <1.8s)
Largest Contentful Paint (LCP)
: Time until largest content element loads (target: <2.5s)
Time to Interactive (TTI)
: Time until page is fully interactive (target: <3.8s)
Total Page Size
: Combined size of all resources (target: <1-3MB)
Number of Requests
: Total HTTP requests made (target: <50)
Performance optimization is a multi-faceted approach that includes:
Caching strategies (page cache, object cache, opcode cache)
Database optimization (query optimization, indexing, cleanup)
Asset optimization (minification, concatenation, lazy loading)
Image optimization (compression, responsive images, modern formats)
Server configuration (PHP version, memory limits, compression)
Content Delivery Networks (CDN) for static assets
Code optimization (efficient queries, conditional loading)
Caching is the most effective way to improve WordPress performance. It stores frequently
accessed data in a faster storage layer, reducing the need to regenerate content or query
the database repeatedly.
Page caching stores the entire HTML output of a page, allowing subsequent requests to be
served instantly without executing PHP or querying the database.
Page Cache Implementation
1<?php
2/**
3 * Simple page cache implementation
4 */
5class Simple_Page_Cache {
6
7 private $cache_dir;
8 private $cache_duration;
9
10 public function __construct() {
11 $this->cache_dir = WP_CONTENT_DIR . '/cache/pages/';
12 $this->cache_duration = 3600; // 1 hour
13
14 // Create cache directory if it doesn't exist
15 if ( ! file_exists( $this->cache_dir ) ) {
16 wp_mkdir_p( $this->cache_dir );
17 }
18
19 // Hook into WordPress
20 add_action( 'template_redirect', array( $this, 'serve_cached_page' ), 1 );
21 add_action( 'shutdown', array( $this, 'cache_page' ) );
22 add_action( 'save_post', array( $this, 'clear_cache' ) );
23 add_action( 'comment_post', array( $this, 'clear_cache' ) );
24 }
25
26 /**
27 * Get cache file path for current request
28 */
29 private function get_cache_file_path() {
30 $url = $_SERVER['REQUEST_URI'];
31 $hash = md5( $url );
32 return $this->cache_dir . $hash . '.html';
33 }
34
35 /**
36 * Serve cached page if available
37 */
38 public function serve_cached_page() {
39 // Don't cache for logged-in users
40 if ( is_user_logged_in() ) {
41 return;
42 }
43
44 // Don't cache POST requests
45 if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
46 return;
47 }
48
49 // Don't cache admin, search, or WooCommerce pages
50 if ( is_admin() || is_search() || is_cart() || is_checkout() ) {
51 return;
52 }
53
54 $cache_file = $this->get_cache_file_path();
55
56 // Check if cache file exists and is still valid
57 if ( file_exists( $cache_file ) ) {
58 $cache_age = time() - filemtime( $cache_file );
59
60 if ( $cache_age < $this->cache_duration ) {
61 // Serve cached content
62 header( 'X-Cache: HIT' );
63 readfile( $cache_file );
64 exit;
65 } else {
66 // Cache expired, delete it
67 unlink( $cache_file );
68 }
69 }
70
71 header( 'X-Cache: MISS' );
72 }
73
74 /**
75 * Cache current page output
76 */
77 public function cache_page() {
78 // Don't cache for logged-in users
79 if ( is_user_logged_in() ) {
80 return;
81 }
82
83 // Don't cache error pages
84 if ( is_404() ) {
85 return;
86 }
87
88 // Get page content
89 $content = ob_get_contents();
90
91 if ( ! empty( $content ) ) {
92 $cache_file = $this->get_cache_file_path();
93 file_put_contents( $cache_file, $content );
94 }
95 }
96
97 /**
98 * Clear all cached pages
99 */
100 public function clear_cache() {
101 $files = glob( $this->cache_dir . '*.html' );
102 foreach ( $files as $file ) {
103 if ( file_exists( $file ) ) {
104 unlink( $file );
105 }
106 }
107 }
108}
109
110// Initialize page cache
111new Simple_Page_Cache();Object caching stores the results of database queries and complex operations, significantly
reducing database load. WordPress has a built-in object cache API that can be backed by
Redis or Memcached.
Object Cache Usage
1<?php
2/**
3 * Using WordPress object cache
4 */
5
6// Set cache
7wp_cache_set( 'my_key', $data, 'my_group', 3600 );
8
9// Get cache
10$data = wp_cache_get( 'my_key', 'my_group' );
11
12if ( false === $data ) {
13 // Cache miss - fetch data
14 $data = expensive_database_query();
15 wp_cache_set( 'my_key', $data, 'my_group', 3600 );
16}
17
18// Delete cache
19wp_cache_delete( 'my_key', 'my_group' );
20
21// Flush entire cache
22wp_cache_flush();
23
24/**
25 * Example: Caching custom queries
26 */
27function get_popular_posts( $limit = 10 ) {
28 $cache_key = 'popular_posts_' . $limit;
29 $posts = wp_cache_get( $cache_key, 'popular_posts' );
30
31 if ( false === $posts ) {
32 $posts = new WP_Query( array(
33 'post_type' => 'post',
34 'posts_per_page' => $limit,
35 'orderby' => 'comment_count',
36 'order' => 'DESC',
37 ) );
38
39 wp_cache_set( $cache_key, $posts, 'popular_posts', 3600 );
40 }
41
42 return $posts;
43}
44
45/**
46 * Clear cache on post update
47 */
48function clear_popular_posts_cache( $post_id ) {
49 wp_cache_delete( 'popular_posts_10', 'popular_posts' );
50 wp_cache_delete( 'popular_posts_20', 'popular_posts' );
51}
52add_action( 'save_post', 'clear_popular_posts_cache' );WordPress Transients API provides a simple way to cache data with expiration times. Transients
are stored in the database by default, but can be moved to object cache with persistent caching.
Transients API
1<?php
2/**
3 * Using Transients for caching
4 */
5
6// Set transient
7set_transient( 'my_transient_key', $data, 3600 );
8
9// Get transient
10$data = get_transient( 'my_transient_key' );
11
12if ( false === $data ) {
13 // Transient expired or doesn't exist
14 $data = fetch_external_api_data();
15 set_transient( 'my_transient_key', $data, 3600 );
16}
17
18// Delete transient
19delete_transient( 'my_transient_key' );
20
21/**
22 * Example: Cache external API calls
23 */
24function get_weather_data( $location ) {
25 $transient_key = 'weather_' . md5( $location );
26 $weather = get_transient( $transient_key );
27
28 if ( false === $weather ) {
29 $api_url = 'https://api.weather.com/data?location=' . urlencode( $location );
30 $response = wp_remote_get( $api_url );
31
32 if ( is_wp_error( $response ) ) {
33 return false;
34 }
35
36 $weather = json_decode( wp_remote_retrieve_body( $response ), true );
37
38 // Cache for 30 minutes
39 set_transient( $transient_key, $weather, 1800 );
40 }
41
42 return $weather;
43}
44
45/**
46 * Site transients (available across entire network in multisite)
47 */
48set_site_transient( 'network_data', $data, 3600 );
49$data = get_site_transient( 'network_data' );
50delete_site_transient( 'network_data' );Fragment caching allows you to cache specific parts of a page while keeping other parts dynamic.
This is useful for caching expensive widgets or sections while maintaining personalized content.
Fragment Cache Helper
1<?php
2/**
3 * Fragment cache helper function
4 */
5function fragment_cache( $key, $ttl, $function ) {
6 $output = get_transient( $key );
7
8 if ( false === $output ) {
9 ob_start();
10 call_user_func( $function );
11 $output = ob_get_clean();
12 set_transient( $key, $output, $ttl );
13 }
14
15 echo $output;
16}
17
18/**
19 * Usage in template
20 */
21fragment_cache( 'sidebar_popular_posts', 3600, function() {
22 ?>
23 <div class="popular-posts">
24 <h3>Popular Posts</h3>
25 <?php
26 $popular = new WP_Query( array(
27 'posts_per_page' => 5,
28 'orderby' => 'comment_count',
29 ) );
30
31 while ( $popular->have_posts() ) : $popular->the_post();
32 ?>
33 <article>
34 <h4><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h4>
35 </article>
36 <?php
37 endwhile;
38 wp_reset_postdata();
39 ?>
40 </div>
41 <?php
42} );Database optimization is crucial for WordPress performance. An optimized database reduces query
execution time, improves page load speed, and reduces server resource usage.
Efficient Queries
1<?php
2/**
3 * Query optimization best practices
4 */
5
6// BAD: Inefficient query
7$args = array(
8 'posts_per_page' => -1, // Don't use -1 unless absolutely necessary
9 'fields' => 'all', // Fetches all fields
10);
11$posts = new WP_Query( $args );
12
13// GOOD: Optimized query
14$args = array(
15 'posts_per_page' => 10, // Limit results
16 'fields' => 'ids', // Only fetch IDs if that's all you need
17 'no_found_rows' => true, // Skip pagination counting
18 'update_post_meta_cache' => false, // Skip meta cache if not needed
19 'update_post_term_cache' => false, // Skip term cache if not needed
20);
21$post_ids = new WP_Query( $args );
22
23// Only fetch specific fields
24global $wpdb;
25$results = $wpdb->get_results(
26 "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type = 'post' LIMIT 10"
27);
28
29/**
30 * Use transients for expensive queries
31 */
32function get_post_count_by_category() {
33 $counts = get_transient( 'category_post_counts' );
34
35 if ( false === $counts ) {
36 global $wpdb;
37
38 $counts = $wpdb->get_results( "
39 SELECT
40 t.term_id,
41 t.name,
42 COUNT(p.ID) as post_count
43 FROM {$wpdb->terms} t
44 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
45 INNER JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
46 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID
47 WHERE
48 tt.taxonomy = 'category' AND
49 p.post_status = 'publish' AND
50 p.post_type = 'post'
51 GROUP BY t.term_id
52 ORDER BY post_count DESC
53 " );
54
55 set_transient( 'category_post_counts', $counts, 3600 );
56 }
57
58 return $counts;
59}
60
61/**
62 * Avoid queries in loops
63 */
64// BAD: Query in loop
65foreach ( $post_ids as $post_id ) {
66 $post = get_post( $post_id ); // Query for each post
67 $meta = get_post_meta( $post_id, 'custom_field', true ); // Another query
68}
69
70// GOOD: Batch queries
71$posts = get_posts( array( 'include' => $post_ids ) );
72$all_meta = get_post_meta( $post_ids ); // Single query for all meta
73
74foreach ( $posts as $post ) {
75 $meta = isset( $all_meta[$post->ID]['custom_field'][0] )
76 ? $all_meta[$post->ID]['custom_field'][0]
77 : '';
78}Proper indexing dramatically improves query performance by allowing the database to find rows
more efficiently.
Adding Custom Indexes
1<?php
2/**
3 * Add custom database indexes
4 */
5function add_custom_database_indexes() {
6 global $wpdb;
7
8 // Add index on post_modified column for recent posts queries
9 $wpdb->query( "
10 ALTER TABLE {$wpdb->posts}
11 ADD INDEX idx_post_modified (post_modified)
12 " );
13
14 // Add composite index for common query patterns
15 $wpdb->query( "
16 ALTER TABLE {$wpdb->posts}
17 ADD INDEX idx_type_status_date (post_type, post_status, post_date)
18 " );
19
20 // Add index on postmeta for frequently queried meta_keys
21 $wpdb->query( "
22 ALTER TABLE {$wpdb->postmeta}
23 ADD INDEX idx_meta_key_value (meta_key(191), meta_value(100))
24 " );
25}
26register_activation_hook( __FILE__, 'add_custom_database_indexes' );
27
28/**
29 * Check if index exists before adding
30 */
31function index_exists( $table, $index_name ) {
32 global $wpdb;
33
34 $result = $wpdb->get_var( $wpdb->prepare( "
35 SELECT COUNT(1)
36 FROM INFORMATION_SCHEMA.STATISTICS
37 WHERE table_schema = DATABASE()
38 AND table_name = %s
39 AND index_name = %s
40 ", $table, $index_name ) );
41
42 return $result > 0;
43}
44
45// Safe index addition
46if ( ! index_exists( $wpdb->posts, 'idx_post_modified' ) ) {
47 $wpdb->query( "ALTER TABLE {$wpdb->posts} ADD INDEX idx_post_modified (post_modified)" );
48}Database Maintenance
1<?php
2/**
3 * Database cleanup utilities
4 */
5class Database_Optimizer {
6
7 /**
8 * Delete post revisions
9 */
10 public static function delete_old_revisions( $days = 30 ) {
11 global $wpdb;
12
13 $date = date( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );
14
15 $wpdb->query( $wpdb->prepare( "
16 DELETE FROM {$wpdb->posts}
17 WHERE post_type = 'revision'
18 AND post_modified < %s
19 ", $date ) );
20 }
21
22 /**
23 * Clean up orphaned metadata
24 */
25 public static function clean_orphaned_metadata() {
26 global $wpdb;
27
28 // Delete orphaned postmeta
29 $wpdb->query( "
30 DELETE pm FROM {$wpdb->postmeta} pm
31 LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
32 WHERE p.ID IS NULL
33 " );
34
35 // Delete orphaned term relationships
36 $wpdb->query( "
37 DELETE tr FROM {$wpdb->term_relationships} tr
38 LEFT JOIN {$wpdb->posts} p ON tr.object_id = p.ID
39 WHERE p.ID IS NULL
40 " );
41
42 // Delete orphaned comment meta
43 $wpdb->query( "
44 DELETE cm FROM {$wpdb->commentmeta} cm
45 LEFT JOIN {$wpdb->comments} c ON cm.comment_id = c.comment_ID
46 WHERE c.comment_ID IS NULL
47 " );
48 }
49
50 /**
51 * Delete spam and trashed comments
52 */
53 public static function clean_comments() {
54 global $wpdb;
55
56 // Delete spam comments older than 30 days
57 $wpdb->query( "
58 DELETE FROM {$wpdb->comments}
59 WHERE comment_approved = 'spam'
60 AND comment_date < DATE_SUB(NOW(), INTERVAL 30 DAY)
61 " );
62
63 // Delete trashed comments
64 $wpdb->query( "
65 DELETE FROM {$wpdb->comments}
66 WHERE comment_approved = 'trash'
67 " );
68 }
69
70 /**
71 * Delete expired transients
72 */
73 public static function clean_transients() {
74 global $wpdb;
75
76 $wpdb->query( "
77 DELETE FROM {$wpdb->options}
78 WHERE option_name LIKE '_transient_timeout_%'
79 AND option_value < UNIX_TIMESTAMP()
80 " );
81
82 // Delete the actual expired transient data
83 $wpdb->query( "
84 DELETE FROM {$wpdb->options}
85 WHERE option_name LIKE '_transient_%'
86 AND option_name NOT LIKE '_transient_timeout_%'
87 AND option_name NOT IN (
88 SELECT REPLACE(option_name, '_transient_timeout_', '_transient_')
89 FROM {$wpdb->options}
90 WHERE option_name LIKE '_transient_timeout_%'
91 AND option_value > UNIX_TIMESTAMP()
92 )
93 " );
94 }
95
96 /**
97 * Optimize database tables
98 */
99 public static function optimize_tables() {
100 global $wpdb;
101
102 $tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}%'" );
103
104 foreach ( $tables as $table ) {
105 $wpdb->query( "OPTIMIZE TABLE $table" );
106 }
107 }
108}
109
110/**
111 * Schedule weekly cleanup
112 */
113function schedule_database_optimization() {
114 if ( ! wp_next_scheduled( 'weekly_database_cleanup' ) ) {
115 wp_schedule_event( time(), 'weekly', 'weekly_database_cleanup' );
116 }
117}
118add_action( 'wp', 'schedule_database_optimization' );
119
120function run_weekly_cleanup() {
121 Database_Optimizer::delete_old_revisions( 30 );
122 Database_Optimizer::clean_orphaned_metadata();
123 Database_Optimizer::clean_comments();
124 Database_Optimizer::clean_transients();
125 Database_Optimizer::optimize_tables();
126}
127add_action( 'weekly_database_cleanup', 'run_weekly_cleanup' );Optimizing CSS, JavaScript, and other assets reduces page size and the number of HTTP requests,
significantly improving load times.
Dequeue Unnecessary Scripts
1<?php
2/**
3 * Remove unnecessary scripts and styles
4 */
5function optimize_assets() {
6 // Remove WordPress emoji scripts
7 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
8 remove_action( 'wp_print_styles', 'print_emoji_styles' );
9
10 // Remove jQuery Migrate
11 function remove_jquery_migrate( $scripts ) {
12 if ( ! is_admin() && isset( $scripts->registered['jquery'] ) ) {
13 $script = $scripts->registered['jquery'];
14 if ( $script->deps ) {
15 $script->deps = array_diff( $script->deps, array( 'jquery-migrate' ) );
16 }
17 }
18 }
19 add_action( 'wp_default_scripts', 'remove_jquery_migrate' );
20
21 // Remove Gutenberg block styles if not using block editor
22 function remove_block_styles() {
23 wp_dequeue_style( 'wp-block-library' );
24 wp_dequeue_style( 'wp-block-library-theme' );
25 wp_dequeue_style( 'wc-block-style' ); // WooCommerce blocks
26 }
27 add_action( 'wp_enqueue_scripts', 'remove_block_styles', 100 );
28
29 // Defer JavaScript loading
30 function defer_scripts( $tag, $handle, $src ) {
31 // Skip if script should not be deferred
32 $skip_defer = array( 'jquery' );
33
34 if ( in_array( $handle, $skip_defer ) ) {
35 return $tag;
36 }
37
38 return str_replace( ' src', ' defer src', $tag );
39 }
40 add_filter( 'script_loader_tag', 'defer_scripts', 10, 3 );
41}
42add_action( 'init', 'optimize_assets' );
43
44/**
45 * Conditionally load scripts only where needed
46 */
47function conditional_scripts() {
48 // Only load Contact Form 7 scripts on contact page
49 if ( ! is_page( 'contact' ) ) {
50 wp_dequeue_script( 'contact-form-7' );
51 wp_dequeue_style( 'contact-form-7' );
52 }
53
54 // Only load WooCommerce scripts on shop pages
55 if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
56 wp_dequeue_style( 'woocommerce-layout' );
57 wp_dequeue_style( 'woocommerce-general' );
58 wp_dequeue_style( 'woocommerce-smallscreen' );
59 wp_dequeue_script( 'wc-cart-fragments' );
60 }
61}
62add_action( 'wp_enqueue_scripts', 'conditional_scripts', 999 );Implement Lazy Loading
1<?php
2/**
3 * Add lazy loading to images
4 */
5function add_lazy_loading( $content ) {
6 // WordPress 5.5+ has native lazy loading
7 // For older versions or custom implementation:
8
9 $content = preg_replace(
10 '/<img(.*?)src=/i',
11 '<img$1loading="lazy" src=',
12 $content
13 );
14
15 return $content;
16}
17add_filter( 'the_content', 'add_lazy_loading' );
18
19/**
20 * Lazy load iframes (YouTube embeds, etc.)
21 */
22function lazy_load_iframes( $content ) {
23 $content = preg_replace(
24 '/<iframe(.*?)src=/i',
25 '<iframe$1loading="lazy" src=',
26 $content
27 );
28
29 return $content;
30}
31add_filter( 'the_content', 'lazy_load_iframes' );
32
33/**
34 * Preload critical assets
35 */
36function preload_critical_assets() {
37 // Preload critical CSS
38 echo '<link rel="preload" href="' . get_stylesheet_uri() . '" as="style">';
39
40 // Preload fonts
41 echo '<link rel="preload" href="' . get_template_directory_uri() . '/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>';
42
43 // Preconnect to external domains
44 echo '<link rel="preconnect" href="https://fonts.googleapis.com">';
45 echo '<link rel="preconnect" href="https://cdn.example.com">';
46}
47add_action( 'wp_head', 'preload_critical_assets', 1 );Inline Critical CSS
1<?php
2/**
3 * Inline critical CSS and defer full CSS
4 */
5function inline_critical_css() {
6 $critical_css = get_template_directory() . '/css/critical.css';
7
8 if ( file_exists( $critical_css ) ) {
9 echo '<style>';
10 include $critical_css;
11 echo '</style>';
12 }
13}
14add_action( 'wp_head', 'inline_critical_css', 1 );
15
16/**
17 * Defer non-critical CSS
18 */
19function defer_css() {
20 ?>
21 <script>
22 function loadCSS(href) {
23 var link = document.createElement('link');
24 link.rel = 'stylesheet';
25 link.href = href;
26 document.head.appendChild(link);
27 }
28
29 // Load non-critical CSS after page load
30 window.addEventListener('load', function() {
31 loadCSS('<?php echo get_stylesheet_uri(); ?>');
32 });
33 </script>
34 <noscript>
35 <link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>">
36 </noscript>
37 <?php
38}
39add_action( 'wp_head', 'defer_css', 999 );Images often account for the majority of page weight. Proper image optimization can dramatically
reduce load times without sacrificing visual quality.
Enable Responsive Images
1<?php
2/**
3 * Add custom image sizes
4 */
5function custom_image_sizes() {
6 // Thumbnail sizes for different viewports
7 add_image_size( 'mobile-small', 320, 240, true );
8 add_image_size( 'mobile-medium', 480, 360, true );
9 add_image_size( 'tablet', 768, 576, true );
10 add_image_size( 'desktop', 1200, 900, false );
11 add_image_size( 'hero', 1920, 1080, true );
12}
13add_action( 'after_setup_theme', 'custom_image_sizes' );
14
15/**
16 * Add WebP support
17 */
18function enable_webp_upload( $mime_types ) {
19 $mime_types['webp'] = 'image/webp';
20 return $mime_types;
21}
22add_filter( 'upload_mimes', 'enable_webp_upload' );
23
24/**
25 * Generate WebP versions of uploaded images
26 */
27function generate_webp_version( $metadata, $attachment_id ) {
28 $file = get_attached_file( $attachment_id );
29
30 if ( ! file_exists( $file ) ) {
31 return $metadata;
32 }
33
34 $info = pathinfo( $file );
35 if ( ! in_array( $info['extension'], array( 'jpg', 'jpeg', 'png' ) ) ) {
36 return $metadata;
37 }
38
39 $webp_file = $info['dirname'] . '/' . $info['filename'] . '.webp';
40
41 // Create WebP version
42 $image = wp_get_image_editor( $file );
43 if ( ! is_wp_error( $image ) ) {
44 $image->set_quality( 80 );
45 $image->save( $webp_file, 'image/webp' );
46 }
47
48 return $metadata;
49}
50add_filter( 'wp_generate_attachment_metadata', 'generate_webp_version', 10, 2 );
51
52/**
53 * Output picture element with WebP
54 */
55function responsive_image_with_webp( $attachment_id, $size = 'large' ) {
56 $image = wp_get_attachment_image_src( $attachment_id, $size );
57
58 if ( ! $image ) {
59 return '';
60 }
61
62 $image_url = $image[0];
63 $webp_url = preg_replace( '/\\.(jpg|jpeg|png)$/i', '.webp', $image_url );
64 $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
65
66 ?>
67 <picture>
68 <source srcset="<?php echo esc_url( $webp_url ); ?>" type="image/webp">
69 <img src="<?php echo esc_url( $image_url ); ?>"
70 alt="<?php echo esc_attr( $alt ); ?>"
71 loading="lazy"
72 width="<?php echo $image[1]; ?>"
73 height="<?php echo $image[2]; ?>">
74 </picture>
75 <?php
76}Automatic Image Compression
1<?php
2/**
3 * Compress images on upload
4 */
5function compress_uploaded_image( $file ) {
6 $image_info = getimagesize( $file['tmp_name'] );
7
8 if ( ! $image_info ) {
9 return $file;
10 }
11
12 $mime_type = $image_info['mime'];
13
14 switch ( $mime_type ) {
15 case 'image/jpeg':
16 $image = imagecreatefromjpeg( $file['tmp_name'] );
17 imagejpeg( $image, $file['tmp_name'], 80 ); // 80% quality
18 imagedestroy( $image );
19 break;
20
21 case 'image/png':
22 $image = imagecreatefrompng( $file['tmp_name'] );
23 imagepng( $image, $file['tmp_name'], 8 ); // Compression level 8
24 imagedestroy( $image );
25 break;
26 }
27
28 return $file;
29}
30add_filter( 'wp_handle_upload_prefilter', 'compress_uploaded_image' );
31
32/**
33 * Set JPEG quality
34 */
35function custom_jpeg_quality( $quality, $context ) {
36 return 80; // 80% quality for good balance
37}
38add_filter( 'jpeg_quality', 'custom_jpeg_quality', 10, 2 );
39add_filter( 'wp_editor_set_quality', 'custom_jpeg_quality', 10, 2 );Proper server configuration is essential for optimal WordPress performance. This includes PHP
settings, compression, and caching headers.
Optimize php.ini Settings
1; Recommended php.ini settings for WordPress
2
3; Memory limit
4memory_limit = 256M
5
6; Maximum execution time
7max_execution_time = 300
8
9; Maximum input time
10max_input_time = 300
11
12; Maximum upload size
13upload_max_filesize = 64M
14post_max_size = 64M
15
16; OPcache settings (PHP 7.0+)
17opcache.enable=1
18opcache.memory_consumption=128
19opcache.interned_strings_buffer=8
20opcache.max_accelerated_files=4000
21opcache.revalidate_freq=60
22opcache.fast_shutdown=1
23
24; Realpath cache
25realpath_cache_size = 4096K
26realpath_cache_ttl = 600Optimized .htaccess
1# BEGIN Performance Optimization
2
3# Enable compression
4<IfModule mod_deflate.c>
5 AddOutputFilterByType DEFLATE text/html
6 AddOutputFilterByType DEFLATE text/css
7 AddOutputFilterByType DEFLATE text/javascript
8 AddOutputFilterByType DEFLATE text/xml
9 AddOutputFilterByType DEFLATE text/plain
10 AddOutputFilterByType DEFLATE application/javascript
11 AddOutputFilterByType DEFLATE application/x-javascript
12 AddOutputFilterByType DEFLATE application/json
13 AddOutputFilterByType DEFLATE application/xml
14 AddOutputFilterByType DEFLATE application/xhtml+xml
15 AddOutputFilterByType DEFLATE application/rss+xml
16 AddOutputFilterByType DEFLATE application/atom_xml
17 AddOutputFilterByType DEFLATE image/svg+xml
18 AddOutputFilterByType DEFLATE font/truetype
19 AddOutputFilterByType DEFLATE font/opentype
20 AddOutputFilterByType DEFLATE application/x-font-ttf
21 AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
22</IfModule>
23
24# Leverage browser caching
25<IfModule mod_expires.c>
26 ExpiresActive On
27
28 # Images
29 ExpiresByType image/jpeg "access plus 1 year"
30 ExpiresByType image/jpg "access plus 1 year"
31 ExpiresByType image/png "access plus 1 year"
32 ExpiresByType image/gif "access plus 1 year"
33 ExpiresByType image/webp "access plus 1 year"
34 ExpiresByType image/svg+xml "access plus 1 year"
35 ExpiresByType image/x-icon "access plus 1 year"
36
37 # CSS and JavaScript
38 ExpiresByType text/css "access plus 1 month"
39 ExpiresByType application/javascript "access plus 1 month"
40 ExpiresByType text/javascript "access plus 1 month"
41
42 # Fonts
43 ExpiresByType font/ttf "access plus 1 year"
44 ExpiresByType font/otf "access plus 1 year"
45 ExpiresByType font/woff "access plus 1 year"
46 ExpiresByType font/woff2 "access plus 1 year"
47 ExpiresByType application/font-woff "access plus 1 year"
48
49 # HTML
50 ExpiresByType text/html "access plus 1 hour"
51</IfModule>
52
53# Set Cache-Control headers
54<IfModule mod_headers.c>
55 # Cache static assets
56 <FilesMatch "\\.(ico|jpg|jpeg|png|gif|webp|svg|css|js|woff|woff2|ttf|otf)$">
57 Header set Cache-Control "public, max-age=31536000"
58 </FilesMatch>
59
60 # Don't cache HTML
61 <FilesMatch "\\.(html|htm)$">
62 Header set Cache-Control "no-cache, no-store, must-revalidate"
63 Header set Pragma "no-cache"
64 Header set Expires "0"
65 </FilesMatch>
66</IfModule>
67
68# Disable ETags
69<IfModule mod_headers.c>
70 Header unset ETag
71</IfModule>
72FileETag None
73
74# END Performance OptimizationPerformance Settings in wp-config.php
1<?php
2/**
3 * Performance optimization settings for wp-config.php
4 */
5
6// Increase WordPress memory limit
7define( 'WP_MEMORY_LIMIT', '256M' );
8define( 'WP_MAX_MEMORY_LIMIT', '512M' );
9
10// Enable Redis object cache
11define( 'WP_REDIS_HOST', 'localhost' );
12define( 'WP_REDIS_PORT', 6379 );
13define( 'WP_CACHE_KEY_SALT', 'your-site-name_' );
14
15// Disable post revisions or limit them
16define( 'WP_POST_REVISIONS', 5 );
17// Or disable completely: define( 'WP_POST_REVISIONS', false );
18
19// Set autosave interval (in seconds)
20define( 'AUTOSAVE_INTERVAL', 300 ); // 5 minutes
21
22// Disable trash (delete immediately)
23// define( 'EMPTY_TRASH_DAYS', 0 );
24
25// Limit trash retention
26define( 'EMPTY_TRASH_DAYS', 7 ); // Empty trash after 7 days
27
28// Optimize database queries
29define( 'WP_ALLOW_REPAIR', true ); // Enable at /wp-admin/maint/repair.php
30
31// Disable file editing in admin
32define( 'DISALLOW_FILE_EDIT', true );
33
34// Set custom upload directory
35// define( 'UPLOADS', 'wp-content/uploads' );
36
37// Increase PHP execution limits for WordPress
38@ini_set( 'memory_limit', '256M' );
39@ini_set( 'max_execution_time', '300' );Content Delivery Networks (CDN) distribute your static assets across multiple servers worldwide,
reducing latency and improving load times for visitors regardless of their geographic location.
CDN URL Rewriting
1<?php
2/**
3 * Rewrite URLs to use CDN
4 */
5class CDN_Rewriter {
6
7 private $cdn_url;
8 private $site_url;
9
10 public function __construct( $cdn_url ) {
11 $this->cdn_url = rtrim( $cdn_url, '/' );
12 $this->site_url = rtrim( site_url(), '/' );
13
14 add_filter( 'wp_get_attachment_url', array( $this, 'rewrite_url' ) );
15 add_filter( 'wp_calculate_image_srcset', array( $this, 'rewrite_srcset' ) );
16 add_filter( 'the_content', array( $this, 'rewrite_content' ) );
17 add_filter( 'style_loader_src', array( $this, 'rewrite_url' ) );
18 add_filter( 'script_loader_src', array( $this, 'rewrite_url' ) );
19 }
20
21 /**
22 * Rewrite single URL
23 */
24 public function rewrite_url( $url ) {
25 if ( empty( $url ) ) {
26 return $url;
27 }
28
29 // Only rewrite URLs from this site
30 if ( strpos( $url, $this->site_url ) !== false ) {
31 $url = str_replace( $this->site_url, $this->cdn_url, $url );
32 }
33
34 return $url;
35 }
36
37 /**
38 * Rewrite srcset URLs
39 */
40 public function rewrite_srcset( $sources ) {
41 if ( empty( $sources ) ) {
42 return $sources;
43 }
44
45 foreach ( $sources as &$source ) {
46 $source['url'] = $this->rewrite_url( $source['url'] );
47 }
48
49 return $sources;
50 }
51
52 /**
53 * Rewrite URLs in content
54 */
55 public function rewrite_content( $content ) {
56 return str_replace( $this->site_url, $this->cdn_url, $content );
57 }
58}
59
60// Initialize CDN rewriter
61new CDN_Rewriter( 'https://cdn.example.com' );
62
63/**
64 * Exclude specific file types from CDN
65 */
66function cdn_excluded_files( $url ) {
67 $excluded_extensions = array( '.php', '.xml' );
68
69 foreach ( $excluded_extensions as $ext ) {
70 if ( strpos( $url, $ext ) !== false ) {
71 return $url; // Don't use CDN for this file
72 }
73 }
74
75 return str_replace( site_url(), 'https://cdn.example.com', $url );
76}
77add_filter( 'wp_get_attachment_url', 'cdn_excluded_files' );Regular performance monitoring helps identify bottlenecks and track improvements over time.
Query Monitor Integration
1<?php
2/**
3 * Custom performance monitoring
4 */
5class Performance_Monitor {
6
7 private $start_time;
8 private $queries = array();
9
10 public function __construct() {
11 $this->start_time = microtime( true );
12
13 add_filter( 'query', array( $this, 'log_query' ) );
14 add_action( 'shutdown', array( $this, 'output_metrics' ) );
15 }
16
17 /**
18 * Log database queries
19 */
20 public function log_query( $query ) {
21 $this->queries[] = array(
22 'query' => $query,
23 'time' => microtime( true ),
24 );
25
26 return $query;
27 }
28
29 /**
30 * Output performance metrics
31 */
32 public function output_metrics() {
33 if ( ! current_user_can( 'manage_options' ) ) {
34 return;
35 }
36
37 $end_time = microtime( true );
38 $execution_time = $end_time - $this->start_time;
39 $memory_usage = memory_get_peak_usage( true ) / 1024 / 1024;
40 $query_count = count( $this->queries );
41
42 ?>
43 <!-- Performance Metrics -->
44 <div style="position: fixed; bottom: 0; left: 0; right: 0; background: #000; color: #fff; padding: 10px; font-family: monospace; font-size: 12px; z-index: 9999;">
45 <strong>Performance Metrics:</strong>
46 Execution Time: <?php echo number_format( $execution_time, 3 ); ?>s |
47 Memory: <?php echo number_format( $memory_usage, 2 ); ?>MB |
48 Queries: <?php echo $query_count; ?>
49 </div>
50 <?php
51 }
52}
53
54// Initialize only for admins in development
55if ( WP_DEBUG && current_user_can( 'manage_options' ) ) {
56 new Performance_Monitor();
57}
58
59/**
60 * Log slow queries
61 */
62function log_slow_queries( $query, $query_time ) {
63 if ( $query_time > 0.05 ) { // Log queries slower than 50ms
64 error_log( sprintf(
65 'Slow Query (%.4fs): %s',
66 $query_time,
67 $query
68 ) );
69 }
70
71 return $query;
72}
73add_filter( 'log_query_custom_data', 'log_slow_queries', 10, 2 );Learn about Best Practices.
Choose managed WordPress hosting with SSD storage, adequate PHP workers, and server-level
caching. Invest in quality hosting for better baseline performance.
Use page caching, object caching (Redis/Memcached), opcode caching (OPcache), and browser
caching. Each layer reduces load on the server and improves response times.
Compress images, use responsive images with srcset, implement lazy loading, and serve
modern formats like WebP. Images are often the largest contributor to page weight.
Combine CSS and JavaScript files, use CSS sprites for icons, inline critical CSS, and
remove unnecessary scripts. Fewer requests mean faster page loads.
Distribute static assets globally using a CDN like Cloudflare, CloudFront, or KeyCDN.
This reduces latency for international visitors and offloads traffic from your server.
Regularly clean up revisions, optimize tables, add proper indexes, and cache query results.
Use efficient queries and avoid queries in loops.
Only use essential, well-coded plugins. Each plugin adds overhead. Regularly audit and
remove unused plugins. Choose quality over quantity.
Use tools like Query Monitor, New Relic, or GTmetrix to track performance metrics.
Regular monitoring helps identify issues before they impact users.
Learn about Practical Exercises.
Create a simple page caching system that stores HTML output and serves it to visitors.
Include cache invalidation when posts are updated.
Requirements:
Cache HTML output to files
Set cache duration (1 hour)
Don't cache for logged-in users
Clear cache on post save
Add X-Cache header (HIT/MISS)
Build a database optimization tool that cleans up old data, adds indexes, and optimizes
tables on a schedule.
Requirements:
Delete post revisions older than 30 days
Clean orphaned postmeta
Delete expired transients
Add custom indexes for common queries
Optimize all database tables
Schedule as weekly WP-Cron job
Log optimization results
Create a comprehensive performance optimization plugin that implements multiple optimization
strategies with an admin settings page.
Requirements:
Page caching with configurable duration
Object caching with Redis/Memcached support
Asset minification and concatenation
Image lazy loading and WebP conversion
Database cleanup scheduler
CDN URL rewriting
Performance monitoring dashboard
Before/after performance metrics
Cache preloading for popular pages
Advanced settings page with toggle options
Learn about Knowledge Check.
Page caching stores entire HTML pages; object caching stores database query results and computed data
They are the same thing
Object caching is faster than page caching
Page caching only works for static sites
Page caching stores the complete HTML output of a page, serving it instantly on subsequent
requests without executing PHP. Object caching stores individual pieces of data like database
query results, API responses, or computed values. Both work together: page cache provides
maximum speed for static content, while object cache optimizes dynamic elements and reduces
database load.
skip_count => true
no_found_rows => true
disable_pagination => true
count_posts => false
The 'no_found_rows' parameter set to true tells WP_Query to skip the SQL_CALC_FOUND_ROWS
calculation, which counts total matching posts for pagination. This extra query can be
expensive on large datasets. If you don't need pagination or total post count, setting this
to true can significantly improve query performance. Other useful performance parameters include
'fields' => 'ids' (only fetch IDs) and 'update_post_meta_cache' => false (skip meta caching).
100% (maximum quality)
80-85% (good quality with compression)
50% (maximum compression)
Quality doesn't matter for web images
A JPEG quality setting of 80-85% provides an optimal balance between visual quality and file
size. At this level, compression artifacts are minimal and imperceptible to most users, while
file size can be 50-70% smaller than 100% quality. Going below 70% usually results in visible
quality loss. WordPress default is 82%, which is excellent. For hero images or photography
portfolios, you might use 85-90%, but 100% is rarely necessary and creates unnecessarily
large files.
Total Time For Building page
Time To First Byte - time from request until first byte received
Time To Full Browser load
Total Transferred File Bytes
Time To First Byte (TTFB) measures the time from when a user makes a request until the first
byte of response data is received by their browser. It includes network latency, server
processing time, and time to generate the response. A good TTFB is under 600ms. High TTFB
usually indicates server-side issues like slow database queries, inefficient code, or server
resource constraints. TTFB is improved through server optimization, caching, CDN usage, and
efficient code execution.
Browser caching
Page caching
Object caching
Opcode caching
Page caching typically has the biggest performance impact because it bypasses the entire
WordPress execution stack - no PHP execution, no database queries, no theme rendering. A cached
page can be served in milliseconds versus seconds for an uncached page. However, all caching
layers are important: browser caching eliminates server requests entirely for repeat visitors,
object caching reduces database load for dynamic content, and opcode caching speeds up PHP
execution. The best strategy uses all layers together for maximum performance.
WordPress Performance Optimization Guide
Official WordPress performance best practices
Web Vitals by Google
Essential metrics for healthy site performance
Query Monitor Plugin
The best debugging and performance plugin for WordPress
Performance testing and analysis tool
Redis Documentation
Learn about Redis for object caching
Your progress is saved in this browser, on this device only.