WordPress Edge Caching with Cloudflare Workers and Cache API1

Cloudflare Workers can intercept every request before it reaches your WordPress origin, check a custom cache key in the Cloudflare Cache API, and serve a cached HTML response from the edge — reducing origin load to near zero for anonymous traffic. Unlike page-caching plugins that still hit PHP, Workers serve from CDN edge nodes globally with sub-millisecond latency.


The Worker below caches WordPress HTML responses for anonymous users, skips caching for logged-in users and WooCommerce cart pages, purges individual URLs on publish via a WordPress hook, and passes cache-status headers for debugging.


// Cloudflare Worker: wp-edge-cache.js
const CACHE_TTL     = 3600;          // 1 hour for regular pages
const BYPASS_PATHS  = [ '/wp-admin', '/wp-login', '/cart', '/checkout', '/my-account' ];
const BYPASS_PARAMS = [ 'preview', 's', 'p', 'page_id' ];

export default {
    async fetch( request, env, ctx ) {
        const url    = new URL( request.url );
        const cache  = caches.default;

        // ── Bypass conditions ───────────────────────────────────────────
        const shouldBypass =
            request.method !== 'GET'
            || BYPASS_PATHS.some( p => url.pathname.startsWith( p ) )
            || BYPASS_PARAMS.some( p => url.searchParams.has( p ) )
            || ( request.headers.get( 'Cookie' ) || '' ).includes( 'wordpress_logged_in' )
            || ( request.headers.get( 'Cookie' ) || '' ).includes( 'woocommerce_items_in_cart' );

        if ( shouldBypass ) {
            const resp = await fetch( request );
            return new Response( resp.body, {
                ...resp,
                headers: { ...Object.fromEntries( resp.headers ), 'x-cache': 'BYPASS' },
            } );
        }

        // ── Check cache ─────────────────────────────────────────────────
        const cacheKey = new Request( url.toString(), { method: 'GET' } );
        let response   = await cache.match( cacheKey );

        if ( response ) {
            return new Response( response.body, {
                ...response,
                headers: { ...Object.fromEntries( response.headers ), 'x-cache': 'HIT' },
            } );
        }

        // ── Cache miss: fetch from origin ───────────────────────────────
        const originResponse = await fetch( request );

        if ( originResponse.status === 200
             && ( originResponse.headers.get( 'content-type' ) || '' ).includes( 'text/html' )
        ) {
            const responseToCache = new Response( originResponse.body, {
                ...originResponse,
                headers: {
                    ...Object.fromEntries( originResponse.headers ),
                    'Cache-Control': `public, max-age=${CACHE_TTL}`,
                    'x-cache':       'MISS',
                },
            } );
            ctx.waitUntil( cache.put( cacheKey, responseToCache.clone() ) );
            return responseToCache;
        }

        return originResponse;
    },
};


 [
                'Authorization' => "Bearer $cf_token",
                'Content-Type'  => 'application/json',
            ],
            'body' => wp_json_encode( [ 'files' => [ $url ] ] ),
        ]
    );
} );


NOTE: The caches.default Cache API in Cloudflare Workers is shared across all Workers in your zone — cache keys are the full URL including query string; ensure your bypass logic strips or normalises UTM parameters and other tracking query strings before using the URL as a cache key, or cache entries will be created for every unique UTM combination.