title: 'Bloques Dinámicos en WordPress 7.0+: render.php, Interactivity API y HTML API' description: >- Guía arquitectónica para la creación de bloques dinámicos e interactivos en WordPress 7.0+ (PHP 8.4+): plantilla render.php con get_block_wrapper_attributes(), islas reactivas impulsadas por @wordpress/interactivity (data-wp-*), manipulación del DOM en streaming vía WP_HTML_Tag_Processor y caché de fragmentos. module: gutenberg-blocks order: 3 difficulty: advanced estimatedTime: 42 min topics:
Los bloques estáticos son idóneos para contenidos editoriales inmutables, pero en aplicaciones corporativas y de comercio electrónico reales, gran parte de la interfaz depende del estado dinámico del servidor: tarjetas de casos de éxito recientes, filtros de catálogo por facetas, calculadoras de precios, bloques personalizados del usuario autenticado o telemetría de infraestructura. Además, incluso para componentes puramente visuales del sistema de diseño, almacenar el HTML generado en wp_posts.post_content plantea un grave problema de mantenibilidad: cambiar una sola clase CSS en miles de páginas exige o bien reescribir la base de datos o bien arrastrar una cadena interminable de versiones deprecated.
Los Bloques Dinámicos (Server-Rendered Blocks) resuelven ambos problemas de raíz. En la base de datos se guarda únicamente un comentario autocerrado con los atributos JSON limpios:
<!-- wp:agentic-lab/live-benchmarks {"category":"edge","limit":4} /-->
En cada petición de página (o al calentar la caché), el núcleo de WordPress 7.0+ deserializa estos atributos y los transfiere a una plantilla aislada del servidor render.php, dotándola automáticamente de tres variables en su ámbito local: $attributes (el array validado según block.json), $content (el HTML renderizado de los bloques hijos InnerBlocks) y $block (la instancia completa de la clase WP_Block, con acceso al contexto heredado $block->context).
Y en WordPress 6.5–7.0+, los bloques dinámicos incorporaron la pieza faltante para el frontend: la Interactivity API nativa (@wordpress/interactivity). En vez de cargar pesados entornos de React o scripts dispares de jQuery, la plantilla render.php anota el HTML con directivas declarativas (data-wp-interactive, data-wp-context, data-wp-on--click, data-wp-bind--hidden), mientras que un micromotor de señales reactivas (~10 KB comprimido) hidrata de inmediato el comportamiento interactivo en el navegador.
Imagina un bloque dinámico en WordPress 7.0+ como un React Server Component (RSC) conectado a señales reactivas en el cliente:
render.php): ejecuta consultas indexadas ultrarrápidas a la BD o a WP_Object_Cache, genera HTML semántico con los tokens de diseño mediante get_block_wrapper_attributes() e inyecta el estado inicial (wp_interactivity_state() / wp_interactivity_data_wp_context()).WP_HTML_Tag_Processor): si antes de la salida se deben añadir atributos ARIA de accesibilidad, clases o loading="lazy" en etiquetas anidadas, el analizador léxico en streaming modifica el HTML en milisegundos sin expresiones regulares y sin sobrecargar la memoria con DOMDocument.viewScriptModule + @wordpress/interactivity): dota de vida a selectores, pestañas y filtros sin requerir recargar la página.[BD: wp_posts.post_content]
<!-- wp:agentic-lab/live-benchmarks {"env":"prod"} /-->
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ FLUJO EN EL SERVIDOR (PHP 8.4+ / render.php) │
│ 1. Comprobación en WP_Object_Cache (¿Fragment Cache Hit?) │
│ 2. Inicialización de estado: wp_interactivity_state(...) │
│ 3. Generación de envoltura: get_block_wrapper_attributes() │
│ 4. Post-procesado de etiquetas con WP_HTML_Tag_Processor │
└────────────────────────┬─────────────────────────────────────────────┘
│ Envío de HTML SSR + Estado JSON
▼
┌──────────────────────────────────────────────────────────────────────┐
│ NAVEGADOR DEL CLIENTE (Módulo ES @wordpress/interactivity) │
│ • Lee data-wp-interactive="agentic/benchmarks" │
│ • Conecta nodos DOM a señales reactivas (data-wp-text, on-click) │
│ • Cambia métricas al instante sin recargar la página (0ms RTT) │
└──────────────────────────────────────────────────────────────────────┘El analizador de bloques extrae los atributos JSON y los fusiona con los valores por defecto del esquema attributes en block.json.
El núcleo incluye el archivo declarado en 'render', inyectando en su ámbito local las variables $attributes, $content y $block (WP_Block).
El procesador del servidor de Interactivity API evalúa directivas data-wp-bind y data-wp-class en PHP, eliminando el Layout Shift (CLS = 0).
La clase WP_HTML_Tag_Processor modifica de forma segura los atributos de las etiquetas en streaming sin crear árboles DOM pesados.
El navegador carga viewScriptModule vía Import Maps y activa los gestores reactivos de store('agentic/benchmarks') ante las interacciones del usuario.
En las arquitecturas de Fullstack Agentic Lab, los bloques dinámicos basados en render.php e Interactivity API se utilizan para:
LCP < 0.8s), mientras el usuario conmuta entornos de infraestructura (Edge CDN / Origin PHP 8.4 / WASM) con un solo clic vía data-wp-on--click sin recargar la página.render.php en el repositorio Git actualiza de inmediato la visualización en 10.000 páginas sin ningún Block Validation Error.WP_HTML_Tag_Processor: inyección automatizada de directivas de accesibilidad (aria-expanded, role), blindaje de enlaces salientes (rel="noopener noreferrer") y vinculación de señales reactivas a bloques hijos.Comparativa entre el bloque estático tradicional, el bloque dinámico heredado con render_callback y el bloque dinámico interactivo moderno en WordPress 7.0+:
A continuación se expone el artefacto de producción de referencia para WordPress 7.0+ (PHP 8.4+ y TypeScript 5.8), ilustrando el ciclo completo de un bloque dinámico interactivo: plantilla de servidor render.php con caché de fragmentos, inicialización de wp_interactivity_state(), manipulación mediante WP_HTML_Tag_Processor y módulo de cliente view.ts con @wordpress/interactivity.
<?php
/**
* PARTE 1: Plantilla de Servidor build/blocks/live-telemetry/render.php (WordPress 7.0+ / PHP 8.4+)
*
* Variables disponibles en el ámbito local de la plantilla:
* @var array<string, mixed> $attributes Atributos validados del bloque según block.json.
* @var string $content HTML generado de los bloques hijos (InnerBlocks).
* @var WP_Block $block Instancia del bloque actual con acceso a $block->context.
*/
declare(strict_types=1);
defined('ABSPATH') || exit;
$clusterRegion = sanitize_key((string) ($attributes['defaultRegion'] ?? 'eu-central-1'));
$showDetails = (bool) ($attributes['showDetails'] ?? true);
// 1. Obtención de datos desde Object Cache (o cálculo y almacenamiento por 5 minutos)
$cacheKey = "agentic_telemetry_{$clusterRegion}";
$telemetry = wp_cache_get($cacheKey, 'agentic_blocks');
if (!is_array($telemetry)) {
$telemetry = [
'eu-central-1' => ['latencyMs' => 12, 'rps' => 6400, 'cacheHitRatio' => '99.4%'],
'us-east-1' => ['latencyMs' => 19, 'rps' => 8120, 'cacheHitRatio' => '98.9%'],
'ap-northeast' => ['latencyMs' => 27, 'rps' => 4300, 'cacheHitRatio' => '99.1%'],
];
wp_cache_set($cacheKey, $telemetry, 'agentic_blocks', 300);
}
// 2. Registro de estado global en el almacén de Interactivity API para hidratación SSR
wp_interactivity_state('agentic-lab/live-telemetry', [
'regions' => $telemetry,
]);
// 3. Creación del contexto local aislado para la instancia del bloque en la página
$blockContext = [
'activeRegion' => $clusterRegion,
'isExpanded' => $showDetails,
];
// 4. Generación de atributos del contenedor (colores, espaciados y clases de supports)
$wrapperAttributes = get_block_wrapper_attributes([
'class' => 'agentic-telemetry-widget',
'data-wp-interactive' => 'agentic-lab/live-telemetry',
]);
// Captura del marcado HTML inicial
ob_start();
?>
<div <?php echo $wrapperAttributes; ?> <?php echo wp_interactivity_data_wp_context($blockContext); ?>>
<div class="agentic-telemetry-widget__controls" role="tablist" aria-label="Selección de centro de datos">
<?php foreach (array_keys($telemetry) as $regionCode) : ?>
<button
type="button"
class="agentic-region-btn"
data-region="<?php echo esc_attr($regionCode); ?>"
data-wp-on--click="actions.selectRegion"
data-wp-class--is-active="callbacks.isRegionActive"
>
<?php echo esc_html(strtoupper($regionCode)); ?>
</button>
<?php endforeach; ?>
</div>
<div class="agentic-telemetry-widget__metrics" data-wp-bind--hidden="!context.isExpanded">
<div class="metric-box">
<span class="metric-label">P95 Latency:</span>
<strong class="metric-value" data-wp-text="callbacks.currentLatency">
<?php echo esc_html((string) $telemetry[$clusterRegion]['latencyMs'] . ' ms'); ?>
</strong>
</div>
<div class="metric-box">
<span class="metric-label">Throughput:</span>
<strong class="metric-value" data-wp-text="callbacks.currentRps">
<?php echo esc_html((string) $telemetry[$clusterRegion]['rps'] . ' req/s'); ?>
</strong>
</div>
<div class="metric-box">
<span class="metric-label">Edge Hit Ratio:</span>
<strong class="metric-value" data-wp-text="callbacks.currentHitRatio">
<?php echo esc_html((string) $telemetry[$clusterRegion]['cacheHitRatio']); ?>
</strong>
</div>
</div>
</div>
<?php
$rawHtml = (string) ob_get_clean();
// 5. WordPress HTML API (WP_HTML_Tag_Processor):
// Enriquecimiento seguro del nodo raíz y botones con metadatos sin regex
$processor = new WP_HTML_Tag_Processor($rawHtml);
if ($processor->next_tag(['class_name' => 'agentic-telemetry-widget'])) {
$processor->set_attribute('data-rendered-at', gmdate('c'));
$processor->set_attribute('data-post-id', (string) ($block->context['postId'] ?? 0));
}
echo $processor->get_updated_html();
/*
* ============================================================================
* PARTE 2: Módulo ES de Cliente src/blocks/live-telemetry/view.ts (TypeScript 5.8)
* Declarado en block.json vía: "viewScriptModule": "file:./view.js"
* ============================================================================
*
* import { store, getContext, getElement } from '@wordpress/interactivity';
*
* interface TelemetryContext {
* activeRegion: string;
* isExpanded: boolean;
* }
*
* interface RegionMetric {
* latencyMs: number;
* rps: number;
* cacheHitRatio: string;
* }
*
* const { state } = store('agentic-lab/live-telemetry', {
* actions: {
* selectRegion() {
* const ctx = getContext<TelemetryContext>();
* const { attributes } = getElement();
* const targetRegion = attributes['data-region'];
* if (typeof targetRegion === 'string') {
* ctx.activeRegion = targetRegion;
* }
* },
* },
* callbacks: {
* isRegionActive(): boolean {
* const ctx = getContext<TelemetryContext>();
* const { attributes } = getElement();
* return ctx.activeRegion === attributes['data-region'];
* },
* currentLatency(): string {
* const ctx = getContext<TelemetryContext>();
* const metrics = state.regions[ctx.activeRegion] as RegionMetric | undefined;
* return metrics ? `${metrics.latencyMs} ms` : 'N/A';
* },
* currentRps(): string {
* const ctx = getContext<TelemetryContext>();
* const metrics = state.regions[ctx.activeRegion] as RegionMetric | undefined;
* return metrics ? `${metrics.rps} req/s` : 'N/A';
* },
* currentHitRatio(): string {
* const ctx = getContext<TelemetryContext>();
* const metrics = state.regions[ctx.activeRegion] as RegionMetric | undefined;
* return metrics ? metrics.cacheHitRatio : 'N/A';
* },
* },
* });
*/
Define en block.json las propiedades 'render': 'file:./render.php', 'viewScriptModule': 'file:./view.js' y activa 'supports': { 'interactivity': true }.
En registerBlockType(), define save: () => null (o save: () => <InnerBlocks.Content /> si contiene bloques hijos).
Emite el contenedor con get_block_wrapper_attributes(), inyecta el contexto local vía wp_interactivity_data_wp_context() y cachea las consultas de base de datos.
Invoca store('namespace/block-name', { actions, callbacks }) de @wordpress/interactivity para gestionar las respuestas interactivas ante las acciones del usuario.