Making WordPress ActivityPub play nice with CloudFlare caching

I like the idea of syndicating the content that I share here across the Fediverse, so the recent announcement of an official WordPress ActivityPub plugin was one of the main reasons I decided to use WordPress when setting up my blog.

Though the plugin still has some rough edges and missing features, overall I think it is working pretty well for what I wanted. When I post on joelencioni.com, I can see my post in my Mastodon feed pretty quickly—though sometimes the formatting isn’t the best but I suppose that’s just the way it is. And, commenting is bi-directional: replies from Mastodon show up as comments on my blog, and replies to those comments from my blog show up as replies on Mastodon. Great!

The ActivityPub plugin watches for when the client sends a request HTTP header that is asking for content with the mime type of “application/activity+json”. If that type of content is requested, then instead of responding with the web page, it will respond with some JSON data meant for machine consumption instead of human consumption. This is how the syndication works, and that all seems fine.

However, I have been bumping into an issue due to the way this all works together with the CDN I chose for page caching, CloudFlare.

The problem is that CloudFlare will cache the first version of the page that is requested and serve that up to everyone going forward, regardless of the type of content being requested.

Normally, this is solved by setting a different HTTP header “Vary: accept” that tells caches that the server will vary its response based on the accept HTTP header. And the ActivityPub plugin recently added a way to easily have this vary header added to the responses.

I enabled this setting last week and thought I was good to go.

Unfortunately, it turns out that CloudFlare does not consider vary values in caching decisions, so this problem was still happening and sometimes breaking my website for some people.

Thankfully, I found a new approach to try. Using CloudFlare workers, I can program the CDN to vary the content based on this header with this bit of code:

export default {
  async fetch(req) {
    const acceptHeader = req.headers.get('accept');
    const url = new URL(req.url);

    if (acceptHeader?.indexOf("application/activity+json") > -1) {
      url.searchParams.append("activitypub", "true");
    }

    return fetch(url.toString(), {
      cf: {
        // Always cache this fetch regardless of content type
        // for a max of 5 minutes before revalidating the resource
        cacheTtl: 300,
        cacheEverything: true,
      },
    });
  }
}

This tells CloudFlare to look at the accept header, and if it has “application/activity+json”, it will add “activitypub=true” to the request query string (the part of the URL after the question mark) behind the scenes, which effectively makes it a different URL. This allows the different content to be cached and served up differently, which I think should solve the issue for me for good. If you still see this problem, please let me know!

Thanks to Dustin Rue for sharing this solution!


Leave a Reply

Your email address will not be published. Required fields are marked *