HTTP Accept header support for Surge cache and ActivityPub

With the HTTP Accept header it’s possible to serve different representations of a resource for the same URI. ActivityPub, the plugin to extend WordPress with some Fediverse features based on the ActivityPub protocol, enhances some default views like author or singular pages with a JSON representation.

JSON representation of an author page.
JSON representation of a single post.

So when using a caching plugin it’s important that these handle this mechanism named content negotiation properly. Otherwise someone using a browser may accidentally get a cached JSON representation instead of the default HTML representation depending on the last request.
Cachify, WP Super Cache or Cache Enabler have already been fixed to not serve cached versions if not a HTML view via the Accept header was requested.

On this site, though, I’m testing Surge which goes a different way. After talking with with Konstantin we decided to try a custom config for variants to define different variants of a cache entry.
We’ll use this to allow Surge caching/serving different representations based on a normalized Accept header (yes, there are a few “default” values):

// Content negotiation.
$representation = 'html'; // Or 'generic'.
if ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
	$accept = strtolower( $_SERVER['HTTP_ACCEPT'] );

	if ( str_contains( $accept, 'text/html' ) ) {
		$representation = 'html';
	} elseif (
		str_contains( $accept, 'application/json' ) ||
		str_contains( $accept, 'application/activity+json' ) ||
		str_contains( $accept, 'application/ld+json' )
	) {
		$representation = 'json';
$config['variants']['representation'] = $representation;
unset( $accept, $representation );

return $config;Code language: PHP (php)

You can place this code in a custom cache configuration file. Then add the constant WP_CACHE_CONFIG with the value of the path to the file in your wp-config.php. For more see this detailed explanation.

Once added, Surge will start caching two/three versions per URI and depending on the Accept header also serve the correct representation.

What caching plugin are using, if any? Let me know if you need help with testing if it supports content negotiation. See you on the other side!