LESSON 2 ⏱️ 14 min read

Caching Strategies

The Caching Stack

Effective WordPress caching happens at multiple levels:

User Request
    ↓
[CDN Cache] ← Edge location
    ↓
[Page Cache] ← Full HTML
    ↓
[Object Cache] ← Database queries
    ↓
[Opcode Cache] ← PHP compilation
    ↓
Database

Page Caching

Page caching stores fully rendered HTML pages, bypassing PHP entirely.

Popular Caching Plugins

PluginBest For
WP Super CacheSimple sites
W3 Total CacheAdvanced users
WP RocketPremium, all-in-one
LiteSpeed CacheLiteSpeed servers
WP Fastest CacheBeginners

Manual Page Caching

// Simple page cache implementation
function my_page_cache_start() {
    $cache_file = WP_CONTENT_DIR . '/cache/' . md5($_SERVER['REQUEST_URI']) . '.html';
    
    if (file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) {
        readfile($cache_file);
        exit;
    }
    
    ob_start();
}

function my_page_cache_end() {
    $cache_file = WP_CONTENT_DIR . '/cache/' . md5($_SERVER['REQUEST_URI']) . '.html';
    file_put_contents($cache_file, ob_get_contents());
    ob_end_flush();
}
Don't Cache Dynamic Content: Exclude shopping carts, user dashboards, and logged-in pages from page caching.

Object Caching

Object caching stores database query results in memory.

Redis/Memcached

// wp-config.php
define('WP_CACHE', true);

// Object cache with Redis
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_DATABASE', 0);

Using Transients

function get_expensive_data() {
    // Try cache first
    $data = get_transient('my_expensive_data');
    
    if (false === $data) {
        // Cache miss - do expensive operation
        $data = expensive_database_query();
        
        // Store for 1 hour
        set_transient('my_expensive_data', $data, HOUR_IN_SECONDS);
    }
    
    return $data;
}

// Clear when data changes
function on_data_updated() {
    delete_transient('my_expensive_data');
}
add_action('save_post', 'on_data_updated');

WordPress Object Cache API

// Store with expiration
wp_cache_set('my_key', $data, 'my_group', 3600);

// Retrieve
$data = wp_cache_get('my_key', 'my_group');

// Delete
wp_cache_delete('my_key', 'my_group');

// Delete entire group
wp_cache_flush_group('my_group');

Browser Caching

Control how browsers cache static assets:

# .htaccess
<IfModule mod_expires.c>
    ExpiresActive On
    
    # Images
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    
    # CSS/JS
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    
    # Fonts
    ExpiresByType font/woff2 "access plus 1 year"
    
    # HTML - don't cache
    ExpiresByType text/html "access plus 0 seconds"
</IfModule>

# Cache-Control headers
<IfModule mod_headers.c>
    <FilesMatch ".(ico|jpg|jpeg|png|gif|webp|svg|js|css|woff2)$">
        Header set Cache-Control "max-age=31536000, public, immutable"
    </FilesMatch>
</IfModule>

CDN Integration

A CDN serves static assets from edge locations worldwide.

Popular CDNs for WordPress

CDNHighlights
CloudflareFree tier, full proxy
BunnyCDNAffordable, fast
KeyCDNPay-as-you-go
StackPathEnterprise features

Basic CDN Setup

// Change asset URLs to CDN
define('WP_CONTENT_URL', 'https://cdn.example.com/wp-content');

// Or use a filter
add_filter('wp_get_attachment_url', function($url) {
    return str_replace(
        'https://example.com',
        'https://cdn.example.com',
        $url
    );
});

Cloudflare Page Rules

Create rules for optimal caching:

# Cache static assets aggressively
URL: *example.com/wp-content/*
Cache Level: Cache Everything
Edge TTL: 1 month

# Bypass cache for admin
URL: *example.com/wp-admin/*
Cache Level: Bypass

Preloading & Hints

<!-- DNS prefetch for third parties -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">

<!-- Preconnect for critical resources -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

<!-- Preload critical assets -->
<link rel="preload" href="/fonts/custom.woff2" as="font" crossorigin>
<link rel="preload" href="/hero.webp" as="image">

<!-- Prefetch next page -->
<link rel="prefetch" href="/next-page/">

Cache Invalidation

// Clear page cache on post update
add_action('save_post', function($post_id) {
    // Clear specific cache
    if (function_exists('wp_cache_delete_page')) {
        wp_cache_delete_page($post_id);
    }
    
    // Clear homepage
    if (function_exists('wp_cache_delete_url')) {
        wp_cache_delete_url(home_url('/'));
    }
});

// Clear all caches
function clear_all_caches() {
    // Object cache
    wp_cache_flush();
    
    // W3 Total Cache
    if (function_exists('w3tc_flush_all')) {
        w3tc_flush_all();
    }
    
    // LiteSpeed Cache
    if (class_exists('LiteSpeed_Cache_API')) {
        LiteSpeed_Cache_API::purge_all();
    }
}

Next Steps

In the next lesson, we'll optimize images - often the biggest performance impact.

🎯 Lesson Complete! You can now implement multi-level caching for WordPress.