Email ico
Cloudflare Worker

Cloudflare workers to bypass cache based on cookie

Devendra Nationalwala | 01 Jan 2022

The load time of your website not only affects your search engine rankings, but is also correlated to the conversion rate on your site. Cloudflare is determined to help website administrators boost the performance of their websites.

Cloudflare users on the Business plan will gain a previously Enterprise-only Page Rule option, Bypass Cache on Cookie. When used in conjunction with a “Cache Everything” Page Rule, this setting allows for websites to cache the HTML of anonymous page visits without affecting dynamic content.

Traffic flow with worker

If your budget doesn't allow you to purchase these paid plans, you can also use workers to bypass cache when a certain cookie is present. However, workers are not 100% free. But they are free for the first 10,000 requests per day. You may explore the pricing here.

Once you create a worker with the following code, create a trigger for URLs you wish to cache in the absence of a cookie.

    // Cookie prefixes that cause a request to bypass the cache when present.
const BYPASS_COOKIE_PREFIXES = [
    "COOKIE_NAME_1",
    "COOKIE_NAME_2"
];

/**
* Main worker entry point.
*/
addEventListener("fetch", event => {
    const request = event.request;
    if (bypassCache(request)) {
        event.respondWith(handleRequest(request));
    }
});

/**
* Do all of the work to bypass the cache
* @param {Request} request - Original request
*/
async function handleRequest(request) {
    // Clone the request so we can add a no-cache, no-store Cache-Control request header.
    let init = {
        method: request.method,
        headers: [...request.headers],
        redirect: "manual",
        body: request.body,
        cf: { cacheTtl: 0 }
    };

    // Use a new URL to tell CF not to use the cache
    let newUrl = await generateNewUrl(request);
    let newRequest = new Request(newUrl, init);
    newRequest.headers.set('Cache-Control', 'no-cache, no-store');

    // Clone the response and add a response header
    let response = await fetch(newRequest);
    if(response.status == 404) {
        // Try again without ?bypasscache=1 parameter
        // The parameter means that Yoast redirect rules don't match so Yoast redirects 
            will never work for logged in users
        newRequest = new Request(request.url, init);
        newRequest.headers.set('Cache-Control', 'no-cache, no-store');
        response = await fetch(newRequest);
    }
    let newResponse = new Response(response.body, response);
    newResponse.headers.set('X-Cookie-Bypass', 'Logged In');
    return newResponse;
}

/**
* Determine if the given request needs to bypass the cache.
* @param {Request} request - inbound request.
* @returns {bool} true if the cache should be bypassed
*/
function bypassCache(request) {
    let needsBypass = false;
    
    // Only bypass for requests to URLs that match one of the URL path patterns
    const url = new URL(request.url);
    const path = url.pathname; // + url.search
    let urlBypass = true;


    // Only bypass if the request contains a cookie that starts with 
        one of the pre-configured prefixes
    let cookieBypass = false;
    if (urlBypass) {
        const cookieHeader = request.headers.get('cookie');
        if (cookieHeader && cookieHeader.length && BYPASS_COOKIE_PREFIXES.length) {
            const cookies = cookieHeader.split(';');
            for (let cookie of cookies) {
                // See if the cookie starts with any of the logged-in user prefixes
                for (let prefix of BYPASS_COOKIE_PREFIXES) {
                    if (cookie.trim().startsWith(prefix)) {
                        cookieBypass = true;
                        break;
                    }
                }
                if (cookieBypass) {
                    break;
                }
            }
        }
    }

    needsBypass = urlBypass && cookieBypass;

    return needsBypass;
}

/**
* Generate new URL
* @param {Request} request - Original request
*/
async function generateNewUrl(request) {
    let url = request.url;
    if (url.indexOf('?') >= 0) {
        url += '&';
    } else {
        url += '?';
    }
    url += 'bypasscache=1';
    return url;
}