Un cache Varnish classique repose sur une hypothèse simple : deux requêtes vers la même URL avec les mêmes en-têtes renvoient le même contenu, donc autant garder ce contenu en mémoire. Cette hypothèse fonctionne très bien avec l’API REST de WordPress, où chaque ressource a sa propre URL (/wp-json/wp/v2/posts/42, /wp-json/wp/v2/pages/7…). Elle s’effondre complètement face à WPGraphQL, où toutes les requêtes, quel que soit leur contenu, arrivent en POST sur la même URL unique, généralement /graphql.
Cette recette explique comment construire une clé de cache pertinente dans ce contexte précis, sans entrer dans le fonctionnement de WPGraphQL Smart Cache, une extension dédiée à ce problème et traitée dans un article ultérieur de ce blog.
Le problème posé par une API GraphQL en POST
Varnish, par défaut, ne met pas en cache les requêtes POST, et n’utilise que l’URL et les en-têtes comme clé de cache pour les requêtes GET. Une requête GraphQL demandant le titre d’un article et une requête demandant l’intégralité de son contenu partagent pourtant la même URL et la même méthode : sans intervention, Varnish ne peut ni les distinguer, ni décider s’il doit renvoyer une réponse déjà en mémoire.
La recette : hasher le corps de la requête comme clé de cache
La solution retenue consiste à extraire le corps de la requête GraphQL, à en calculer une empreinte, puis à l’utiliser comme partie de la clé de cache Varnish, dans le fichier de configuration VCL :
sub vcl_recv {
if (req.url == "/graphql" && req.method == "POST") {
# On lit le corps pour pouvoir le hasher plus loin
std.cache_req_body(500KB);
}
}
sub vcl_hash {
if (req.url == "/graphql") {
hash_data(req.http.X-Corps-GraphQL);
} else {
hash_data(req.url);
}
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
}
Le corps de la requête est récupéré côté application, dans un en-tête personnalisé X-Corps-GraphQL ajouté juste avant que Varnish n’établisse sa clé de hachage, par exemple via un petit module Node.js placé devant WordPress qui recopie le corps de la requête POST dans cet en-tête avant de la transmettre à Varnish.

Forcer le passage en GET pour les requêtes en lecture seule
Une alternative plus simple, quand le front le permet, consiste à transformer les requêtes de lecture en GET avec la requête et les variables encodées dans les paramètres d’URL, ce que WPGraphQL accepte nativement :
GET /graphql?query=query%20Post(%24id%3AID!)%7Bpost(id%3A%24id)%7Btitle%7D%7D&variables=%7B%22id%22%3A%2242%22%7D
Cette approche redonne à Varnish son fonctionnement natif basé sur l’URL, sans configuration VCL personnalisée. Elle a néanmoins une limite pratique : les URLs générées deviennent rapidement très longues pour des requêtes complexes, avec un risque de dépassement des limites de longueur d’URL imposées par certains serveurs ou proxys intermédiaires.
Exclure systématiquement les mutations du cache
Quelle que soit la stratégie retenue pour les lectures, toute mutation GraphQL doit rester strictement exclue du cache, sous peine de renvoyer une réponse obsolète à un client qui vient pourtant de modifier une donnée :
sub vcl_recv {
if (req.http.X-Corps-GraphQL ~ "mutation") {
return (pass);
}
}
Cette vérification, volontairement simple sur une chaîne de caractères, suffit dans la pratique : une requête GraphQL de lecture ne contient jamais le mot-clé mutation à sa racine, une mutation commence toujours explicitement par ce mot-clé.
Purger le cache après une modification de contenu
Le cache par hash de requête pose un problème supplémentaire pour l’invalidation : impossible de purger « l’article numéro 42 » puisque la clé de cache ne porte plus l’identifiant de la ressource de façon lisible. La solution retenue associe, à chaque réponse mise en cache, un en-tête personnalisé listant les identifiants de contenu concernés, exploité ensuite par un système de purge par balise (ban Varnish) plutôt que par URL :
sub vcl_backend_response {
set beresp.http.X-Cache-Tags = "post-42,author-3";
}
Un webhook déclenché à chaque publication WordPress envoie ensuite une commande de purge Varnish ciblant la balise correspondante, sans devoir reconstruire la clé de hachage exacte de chaque requête concernée.
Les précautions à ne pas oublier
- Vérifier que le corps de requête lu par
std.cache_req_bodyreste sous la limite fixée, une requête GraphQL trop volumineuse serait tronquée silencieusement. - Ne jamais mettre en cache une réponse contenant des données spécifiques à un utilisateur authentifié, en excluant explicitement les requêtes porteuses d’un en-tête
Authorization. - Surveiller le taux de succès du cache (
hit ratio) après mise en place, une clé de hachage trop spécifique peut réduire drastiquement l’efficacité du cache par rapport à ce qui était espéré.
Mettre en cache une API GraphQL demande d’accepter une idée à contre-courant des habitudes REST : la clé de cache n’est plus l’URL, c’est la question posée elle-même.
En résumé
Le caractère unique de l’URL GraphQL n’est pas un obstacle insurmontable pour Varnish, mais il oblige à repenser la clé de cache autour du contenu de la requête plutôt que de son adresse. Entre le hash du corps de requête et le passage en GET pour les lectures simples, chaque projet doit choisir selon la complexité de ses requêtes et sa tolérance à une configuration VCL plus élaborée.