WordPress 10 min de lecture

Bloc Gutenberg dynamique sans build: un indicateur d’état de service rendu côté serveur

#gutenberg#php#wordpress

Apprenez à créer un bloc Gutenberg WordPress SSR sans build: interroger un endpoint JSON, cache PHP minimal, éditeur réactif via ServerSideRender

Bloc Gutenberg dynamique sans build: un indicateur d’état de service rendu côté serveur

Dans ce tutoriel, tu vas créer un bloc Gutenberg qui affiche l’état d’un service externe à partir d’un endpoint JSON, sans toolchain front (pas de Webpack/Babel). Le bloc est rendu côté serveur (SSR), mis en cache, et propose un aperçu en direct dans l’éditeur via ServerSideRender. Tu auras un plugin minimal, des fichiers simples à maintenir, et un résultat propre côté front et éditeur.

Objectif

L’objectif est de construire un bloc WordPress « État du service » qui interroge un endpoint JSON et affiche son statut. Le cœur du rendu est en PHP (Server-Side Render), ce qui évite toute compilation front. Le bloc expose des options dans l’éditeur (URL de l’endpoint, TTL de cache, témoin visuel), met en cache la réponse pour limiter la charge réseau, et se met à jour en direct dans l’éditeur grâce au composant ServerSideRender.

Objectif et prérequis

Tu vas créer un bloc Gutenberg SSR « État du service » qui lit un endpoint JSON idéalement de la forme {"status":"up","message":"Service opérationnel"}. Le bloc va mettre en cache le rendu pour une durée configurable, et ne requiert aucune chaîne d’outillage JavaScript. Tout se base sur block.json, un peu de PHP, un JS minimal compatible avec l’éditeur, et deux fichiers CSS.

Côté prérequis, il te faut un WordPress 6.x en place, PHP 8.1+ recommandé, un accès aux fichiers du site, et quelques notions de base sur les blocs (block.json, register_block_type) ainsi qu’une lecture de PHP et JS sans build. WP-CLI est facultatif mais utile pour vider les transients en local.

Arborescence du plugin

Commence par créer le dossier du plugin dans wp-content/plugins, puis les fichiers nécessaires. Tu peux le faire en ligne de commande ou via un éditeur. L’idée est d’avoir un répertoire acme-status-block contenant le bootstrap du plugin, les métadonnées du bloc (block.json), un JS d’éditeur, le rendu PHP, et deux feuilles de style.

Exemple avec bash (en local ou via SSH) :

cd wp-content/plugins
mkdir -p acme-status-block
cd acme-status-block
touch acme-status-block.php block.json index.js render.php style.css editor.css

Le fichier acme-status-block.php enregistre le bloc, block.json décrit ses attributs et références de fichiers, index.js ne sert qu’à afficher l’UI dans l’éditeur et le rendu via ServerSideRender, render.php produit le HTML côté serveur, et style.css/editor.css stylent l’affichage côté front et éditeur.

block.json: métadonnées et attributs

Crée le fichier block.json avec le contenu suivant. Il déclare le nom du bloc, son icône, ses attributs, et indique à WordPress où trouver les fichiers JS/CSS et le rendu PHP.

{
  "apiVersion": 2,
  "name": "acme/status",
  "title": "État du service",
  "category": "widgets",
  "icon": "heart",
  "description": "Affiche l'état d'un endpoint JSON (status/message)",
  "supports": { "anchor": true, "align": ["wide", "full"] },
  "attributes": {
    "endpoint": { "type": "string", "default": "" },
    "ttl": { "type": "number", "default": 60 },
    "showIcon": { "type": "boolean", "default": true }
  },
  "editorScript": "file:./index.js",
  "style": "file:./style.css",
  "editorStyle": "file:./editor.css",
  "render": "file:./render.php"
}

L’attribut endpoint contiendra l’URL de l’API renvoyant du JSON. L’attribut ttl correspond à la durée de cache en secondes (0 désactive le cache pendant l’édition ou le front). L’attribut showIcon permet d’afficher un petit témoin visuel de statut. La clé render pointe vers un fichier PHP qui retournera le HTML final du bloc.

Exemple d’endpoint attendu côté API :

{ "status": "up", "message": "Service opérationnel" }

index.js: UI éditeur sans toolchain (ServerSideRender)

Le fichier index.js enregistre le bloc dans l’éditeur et propose une UI simple dans le panneau latéral. Il n’emploie pas de JSX, uniquement wp.element.createElement, et délègue l’affichage à wp.serverSideRender pour prévisualiser le rendu réel côté serveur.

Copie ce contenu dans acme-status-block/index.js :

const { registerBlockType } = wp.blocks;
const { InspectorControls } = wp.blockEditor;
const { PanelBody, TextControl, ToggleControl } = wp.components;
const ServerSideRender = wp.serverSideRender;

registerBlockType('acme/status', {
  edit: ({ attributes, setAttributes }) => (
    wp.element.createElement(wp.element.Fragment, {},
      wp.element.createElement(InspectorControls, {},
        wp.element.createElement(PanelBody, { title: 'Options' },
          wp.element.createElement(TextControl, { label: 'Endpoint JSON', value: attributes.endpoint, onChange: (v) => setAttributes({ endpoint: v }) }),
          wp.element.createElement(TextControl, { label: 'TTL (s)', type: 'number', value: attributes.ttl, onChange: (v) => setAttributes({ ttl: parseInt(v || 0, 10) }) }),
          wp.element.createElement(ToggleControl, { label: 'Afficher icône', checked: attributes.showIcon, onChange: (v) => setAttributes({ showIcon: v }) })
        )
      ),
      wp.element.createElement(ServerSideRender, { block: 'acme/status', attributes })
    )
  ),
  save: () => null
});

Dès que tu modifies l’URL, le TTL ou l’option d’icône, le composant ServerSideRender rafraîchit automatiquement l’aperçu pour refléter le résultat réel retourné par render.php.

render.php: rendu serveur + mise en cache

Le rendu serveur reçoit les attributs, appelle l’endpoint si défini, interprète la réponse pour décider du statut, puis construit le HTML. Pour éviter de solliciter l’API à chaque requête, la sortie HTML est mise en cache via un transient, paramétrable avec ttl. Un TTL à 0 désactive le cache à la volée.

Copie ce contenu dans acme-status-block/render.php :

<?php
$endpoint   = isset($attributes['endpoint']) ? esc_url_raw($attributes['endpoint']) : '';
$ttl        = isset($attributes['ttl']) ? max(0, intval($attributes['ttl'])) : 60;
$show_icon  = ! empty($attributes['showIcon']);
$key        = 'acme_status_' . md5($endpoint . '|' . (int) $show_icon);

$html = $ttl ? get_transient($key) : false;

if (false === $html) {
  $status  = 'unknown';
  $message = '';

  if ($endpoint) {
    $res = wp_remote_get($endpoint, array('timeout' => 3));
    if (!is_wp_error($res)) {
      $code = wp_remote_retrieve_response_code($res);
      $body = wp_remote_retrieve_body($res);
      $data = json_decode($body, true);

      if (is_array($data) && isset($data['status'])) {
        $status  = sanitize_key($data['status']);
        $message = isset($data['message']) ? wp_kses_post($data['message']) : '';
      } else {
        $status  = ($code >= 200 && $code < 300) ? 'up' : 'down';
        $message = 'HTTP ' . intval($code);
      }
    } else {
      $status  = 'down';
      $message = esc_html($res->get_error_message());
    }
  }

  $classes = 'wp-block-acme-status status-' . $status;
  $icon    = $show_icon ? '<span class="dot"></span>' : '';
  $html    = '<div class="' . esc_attr($classes) . '">' . $icon . '<strong>' . esc_html(ucfirst($status)) . '</strong> ' . $message . '</div>';

  if ($ttl) {
    set_transient($key, $html, $ttl);
  }
}

echo $html;

Si l’API renvoie une structure reconnue avec status et message, elle est utilisée telle quelle (avec échappement des sorties). À défaut, le code HTTP sert de repli pour décider si le service est « up » ou « down ». Les entrées sont nettoyées (esc_url_raw, sanitize_key) et les sorties échappées (esc_html, esc_attr, wp_kses_post) pour des raisons de sécurité.

Bootstrap du plugin

Le fichier principal du plugin enregistre le bloc à l’initialisation en s’appuyant sur block.json. Place ce contenu dans acme-status-block/acme-status-block.php :

<?php
/**
 * Plugin Name: ACME Status Block
 * Description: Bloc SSR d'état de service.
 * Author: ACME
 * Version: 0.1.0
 * Requires at least: 6.0
 * Requires PHP: 8.1
 * Text Domain: acme-status-block
 */
add_action('init', function () {
  register_block_type(__DIR__);
});

register_block_type(DIR) lit block.json, enregistre automatiquement les scripts/styles, et associe le rendu PHP.

Styles de base (front et éditeur)

Ajoute un style minimal pour rendre l’alerte lisible et colorée, avec un point visuel pour l’état si l’option est active. Copie le CSS suivant dans acme-status-block/style.css :

.wp-block-acme-status{display:flex;align-items:center;gap:.5rem;padding:.75rem;border-radius:.5rem;background:#f6f7f9}
.wp-block-acme-status .dot{width:.6rem;height:.6rem;border-radius:50%;display:inline-block}
.status-up .dot{background:#22c55e}
.status-down .dot{background:#ef4444}
.status-unknown .dot{background:#a8a29e}

Pour améliorer la cohérence entre l’éditeur et le front, tu peux copier le même contenu dans acme-status-block/editor.css. L’aperçu dans Gutenberg reflétera alors le rendu réel, grâce à ServerSideRender et aux mêmes styles.

Tester rapidement le bloc

Active le plugin depuis l’interface d’administration de WordPress, menu Extensions. Crée ou édite une page, ajoute le bloc « État du service », puis renseigne un endpoint JSON qui retourne au minimum {"status":"up"}. Si tu n’as pas d’API sous la main, tu peux créer un point d’accès de test interne à WordPress pour le développement. Ajoute ce code dans acme-status-block/acme-status-block.php, après l’action init :

add_action('rest_api_init', function(){
  register_rest_route('acme/v1','/status', array(
    'methods'             => 'GET',
    'permission_callback' => '__return_true',
    'callback'            => function(){
      return array(
        'status'  => (rand(0,1) ? 'up' : 'down'),
        'message' => 'Mock'
      );
    }
  ));
});

Utilise alors l’URL suivante comme endpoint dans le bloc: /wp-json/acme/v1/status. Le TTL par défaut de 60 secondes met en cache la sortie HTML. Pour vérifier le comportement du cache, mets le TTL à 0 pour désactiver temporairement le cache et forcer un nouvel appel à l’API à chaque rendu, puis remets-lui une valeur non nulle. Tu peux aussi purger les transients en local via WP-CLI :

wp transient delete --all

Changer l’URL modifie la clé de cache et force un nouveau rendu. Changer le TTL à 0 contourne le cache immédiatement pour l’aperçu et le front, sans supprimer les transients existants.

Robustesse et variantes

En production, choisis un délai réseau raisonnable pour les appels HTTP, par exemple 3 à 5 secondes. Tu peux journaliser les erreurs en environnement de staging via error_log pour diagnostiquer des endpoints instables, tout en évitant de polluer les logs en production. Exemple d’ajout simple :

// Dans render.php, après wp_remote_get
if (is_wp_error($res)) {
  if (defined('WP_DEBUG') && WP_DEBUG) {
    error_log('[acme-status] ' . $res->get_error_message());
  }
}

Pour le cache, le transient convient largement. Si ton site dispose d’un object cache persistant (Redis/Memcached), tu peux basculer sur wp_cache_get/wp_cache_set afin d’obtenir des performances homogènes, en conservant une clé déterminée par les attributs utilisés pour le rendu. Par exemple :

$cache_group = 'acme_status';
$html = $ttl ? wp_cache_get($key, $cache_group) : false;
// ...
if ($ttl) {
  wp_cache_set($key, $html, $cache_group, $ttl);
}

Si l’API renvoie des en-têtes ETag ou Last-Modified, exploite le HTTP conditionnel pour limiter la bande passante et prolonger le cache en cas de 304. Tu peux stocker l’ETag dans un transient parallèle et l’envoyer dans If-None-Match au prochain appel. Exemple simplifié :

$meta_key = 'acme_status_meta_' . md5($endpoint);
$etag = get_transient($meta_key);
$args = array('timeout' => 3, 'headers' => array());
if ($etag) {
  $args['headers']['If-None-Match'] = $etag;
}
$res = wp_remote_get($endpoint, $args);
if (!is_wp_error($res)) {
  $code = wp_remote_retrieve_response_code($res);
  if ($code === 304 && $ttl) {
    // Prolonge le cache existant sans refaire le HTML
    set_transient($key, get_transient($key), $ttl);
  } else {
    $new_etag = wp_remote_retrieve_header($res, 'etag');
    if ($new_etag) {
      set_transient($meta_key, $new_etag, $ttl ?: 60);
    }
    // Traite le JSON comme dans l’exemple principal...
  }
}

Pour l’accessibilité, veille à toujours afficher un libellé textuel explicite (« Up », « Down », « Unknown ») en plus du témoin de couleur. Si tu ajoutes un rafraîchissement côté client dans le futur, envisage aria-live="polite" pour annoncer les mises à jour aux technologies d’assistance.

Pour l’internationalisation, déclare le textdomain dans l’en-tête du plugin (déjà présent) et entoure les libellés côté PHP de __(). Voici un exemple minimal d’adaptation du libellé de statut :

$label = ucfirst($status);
$label = $status === 'up' ? __('Up', 'acme-status-block')
       : ($status === 'down' ? __('Down', 'acme-status-block')
       : __('Unknown', 'acme-status-block'));
$html  = '<div class="' . esc_attr($classes) . '">' . $icon . '<strong>' . esc_html($label) . '</strong> ' . $message . '</div>';

Côté éditeur, tu peux utiliser wp.i18n.__ sans build :

const { __ } = wp.i18n;
// ...
wp.element.createElement(PanelBody, { title: __('Options', 'acme-status-block') }, /* ... */)

Enfin, pour l’extensibilité, ajoute par exemple un bouton « Rafraîchir » dans l’éditeur qui modifie un attribut factice (timestamp) afin d’invalider la clé de cache côté render.php. Tu peux aussi proposer des variations d’affichage (compact, détaillé) via un nouvel attribut layout et des classes CSS associées.

Checklist

Avant de publier, relis le code et teste les cas d’erreur (endpoint vide, JSON invalide, timeout). Vérifie que l’aperçu éditeur reflète bien le rendu front et que le cache respecte le TTL. En local, purge les transients ou change l’URL pour valider la rotation du cache, puis active le plugin en environnement de test et enfin en production lorsque tu es satisfait.

Conclusion

Tu disposes maintenant d’un bloc Gutenberg SSR léger et robuste, qui interroge un endpoint JSON, met en cache le rendu, et s’aperçoit en direct dans l’éditeur, le tout sans outil de build. Cette approche réduit la complexité tout en restant performante et sécurisée. À partir de cette base, tu peux enrichir le bloc avec des variantes d’affichage, des rafraîchissements conditionnels, un HTTP conditionnel avancé, et une internationalisation complète.

Ressources