Un client nous a signalé un temps de chargement anormal sur ses pages produits. Le coupable : un bloc dynamique qui affichait la météo locale d’un point de vente en interrogeant une API tierce à chaque rendu, y compris pour les visiteurs anonymes qui rechargeaient la même page dix fois par minute. Le render_callback faisait un appel HTTP bloquant de 300 à 400 ms, sans aucune protection.
Le cache de rendu d’un bloc dynamique côté serveur répond exactement à ce cas : des données qui changent peu, un calcul ou un appel réseau coûteux, et un contenu identique pour tous les visiteurs (ou presque). Ce n’est pas du cache de page complète — ici on cible uniquement le fragment HTML produit par un bloc précis, avec une clé et une durée de vie qui lui sont propres.
Le problème posé simplement
Depuis WordPress 6.1, un bloc dynamique peut pointer vers un fichier render.php déclaré dans block.json via la clé render, en plus du render_callback classique enregistré par register_block_type(). Dans les deux cas, la fonction de rendu s’exécute à chaque appel de render_block(), donc potentiellement plusieurs fois par chargement de page si le bloc est répété, et à chaque visite si aucune couche de cache de page n’intercepte la requête.
Sans protection, un bloc qui fait une requête SQL complexe, un appel wp_remote_get() ou une agrégation coûteuse pénalise directement le temps de réponse serveur, et donc le Time to First Byte que Google surveille depuis l’annonce des Core Web Vitals en mai 2020.
Choisir la bonne clé de cache
La clé de cache doit refléter tout ce qui peut faire varier le HTML produit : les attributs du bloc, l’ID du post qui l’héberge, et éventuellement le contexte de bloc (context) si le rendu en dépend. Une clé trop large mélange des résultats différents ; une clé trop étroite recalcule inutilement.
function agence_meteo_render_callback( $attributes, $content, $block ) {
$point_de_vente = $attributes['pointDeVente'] ?? '';
$cle_cache = 'meteo_pdv_' . md5( $point_de_vente );
$html = get_transient( $cle_cache );
if ( false !== $html ) {
return $html;
}
$reponse = wp_remote_get(
'https://api.meteo-exemple.fr/v1/point/' . rawurlencode( $point_de_vente ),
array( 'timeout' => 3 )
);
if ( is_wp_error( $reponse ) || 200 !== wp_remote_retrieve_response_code( $reponse ) ) {
// On ne met jamais en cache une erreur : on réessaiera au prochain hit.
return '<p class="meteo-indisponible">Météo momentanément indisponible.</p>';
}
$donnees = json_decode( wp_remote_retrieve_body( $reponse ), true );
$html = sprintf(
'<div class="meteo-pdv">%s — %s°C</div>',
esc_html( $donnees['libelle'] ?? '' ),
esc_html( $donnees['temperature'] ?? '' )
);
set_transient( $cle_cache, $html, 15 * MINUTE_IN_SECONDS );
return $html;
}
Le point important : on ne met jamais en cache une réponse d’erreur. Si l’API tierce est en panne, on préfère réessayer au hit suivant plutôt que de figer un message d’indisponibilité pendant quinze minutes.

Transient ou objet cache persistant
set_transient() et get_transient() suffisent sur la plupart des hébergements, car WordPress les stocke automatiquement dans l’objet cache si un plugin comme Redis Object Cache ou Memcached est actif, sinon en table wp_options. Sur un hébergement mutualisé sans objet cache persistant, chaque transient ajoute deux lignes dans wp_options (la valeur et son expiration), ce qui peut alourdir la table si le bloc génère beaucoup de clés différentes (un point de vente par ville, par exemple).
Pour un site à fort trafic avec Redis déjà en place, on préfère parfois wp_cache_set() et wp_cache_get() directement, avec un groupe de cache dédié :
wp_cache_set( $cle_cache, $html, 'blocs_meteo', 15 * MINUTE_IN_SECONDS );
$html = wp_cache_get( $cle_cache, 'blocs_meteo' );
La différence : un groupe de cache dédié se purge d’un bloc si besoin (wp_cache_flush_group() sur les objets cache qui le supportent), sans toucher aux transients d’autres fonctionnalités du site.
Invalider au bon moment
Le piège classique : un contenu mis à jour dans l’admin qui n’apparaît pas en front parce que le cache n’a pas été purgé. Pour un bloc dont les données viennent d’un custom post type interne (pas d’une API externe), on invalide sur l’action save_post du post type concerné plutôt que d’attendre l’expiration :
- Identifier précisément quel
save_post_{post_type}déclenche la modification des données affichées par le bloc. - Supprimer uniquement les clés de cache concernées, jamais un
wp_cache_flush()global qui viderait aussi les caches d’objets, de requêtes et de sessions d’autres extensions. - Si la clé dépend d’un identifiant que l’on ne connaît pas facilement au moment de la sauvegarde, ajouter un préfixe commun et parcourir les transients par requête SQL ciblée sur
wp_options, en dernier recours seulement.
Sur nos projets, on documente systématiquement dans un commentaire au-dessus du
render_callbackla durée de cache choisie et la raison : « 15 minutes, données météo, tolérance acceptée par le client ». Six mois plus tard, personne ne se souvient pourquoi on avait choisi cette valeur sans cette ligne.
Ce que le cache de rendu ne résout pas
Cette technique cible un bloc précis, pas la page entière. Si le site utilise déjà un cache de page complet (WP Rocket, W3 Total Cache, ou un cache serveur type Varnish), le rendu du bloc n’est de toute façon calculé qu’une fois par intervalle de purge de page — le cache de bloc devient alors surtout utile pour l’admin (aperçu de l’éditeur via ServerSideRender) ou pour les pages exclues du cache de page, comme celles avec des paramètres de requête dynamiques.
Attention également au contexte utilisateur : si le rendu du bloc change selon que le visiteur est connecté ou non (affichage d’un badge « membre », par exemple), il faut absolument inclure cet état dans la clé de cache, sous peine d’afficher le contenu d’un visiteur à un autre.
En résumé
Mettre en cache le rendu d’un bloc dynamique coûteux se résume à trois règles : une clé qui capture tout ce qui fait varier le HTML, une durée de vie assumée et documentée, et une invalidation ciblée plutôt qu’un flush global. Sur le bloc météo de ce projet, le passage à un transient de quinze minutes a fait chuter le temps de rendu moyen de 380 ms à quelques microsecondes pour l’immense majorité des visites, sans jamais afficher de donnée périmée de plus d’un quart d’heure.