Caching JSON with Cloudflare

The standard Cloudflare cache level (under your domain's Performance Settings) is set to Standard/Aggressive, meaning it caches only certain types by default scripts, stylesheets, images. Aggressive caching won't cache normal web pages (ie at a directory location or *.html) and won't cache JSON. All of this is based on the URL pattern (e.g. does it end in .jpg?) and regardless of the Content-Type header.

The global setting can only be made less aggressive, not more, so you'll need to setup one or more Page Rules to match those URLs, using Cache Everything as the custom cache rule.

http://blog.cloudflare.com/introducing-pagerules-advanced-caching

BTW I wouldn't recommend using an HTML Content-Type for a JSON response.


By default, Cloudflare does not cache JSON file. I've ended up with config a new page rule:

https://example.com/sub-directiory/*.json*
  • Cache level: Cache Everything
  • Browser Cache TTL: set a timeout
  • Edge Cache TTL: set a timeout

Cloudflare page rule for json

Hope it saves someone's day.


The new workers feature ($5 extra) can facilitate this:

Important point: Cloudflare normally treats normal static files as pretty much never expiring (or maybe it was a month - I forget exactly).

So at first you might think "I just want to add .json to the list of static extensions". This is likely NOT want you want with JSON - unless it really rarely changed - or is versioned by filename. You probably want something like 60 seconds or 5 minutes so that if you update a file it'll update within that time but your server won't get bombarded with individual requests from every end user.

Here's how I did this with a worker to intercept all .json extension files:

// Note: there could be tiny cut and paste bugs in here - please fix if you find!
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event));
});

async function handleRequest(event)
{
  let request = event.request;
  let ttl = undefined;
  let cache = caches.default;      
  let url = new URL(event.request.url);

  let shouldCache = false;


  // cache JSON files with custom max age
  if (url.pathname.endsWith('.json'))
  {
    shouldCache = true;
    ttl = 60;
  }

  // look in cache for existing item
  let response = await cache.match(request);

  if (!response) 
  {       
    // fetch URL
    response = await fetch(request);

    // if the resource should be cached then put it in cache using the cache key
    if (shouldCache)
    {
      // clone response to be able to edit headers
      response = new Response(response.body, response);

      if (ttl) 
      {
        // https://developers.cloudflare.com/workers/recipes/vcl-conversion/controlling-the-cache/
        response.headers.append('Cache-Control', 'max-age=' + ttl);
      }

      // put into cache (need to clone again)
      event.waitUntil(cache.put(request, response.clone()));
    }

    return response;
  }

  else {
    return response;
  }
}

You could do this with mime-type instead of extension - but it'd be very dangerous because you'd probably end up over-caching API responses.

Also if you're versioning by filename - eg. products-1.json / products-2.json then you don't need to set the header for max-age expiration.

Tags:

Cloudflare