---
title: "Building Dynamic Gutenberg Blocks Consuming External REST APIs: React, SSR & Caching"
description: "Master developing custom WordPress block editor (Gutenberg) blocks using React, block.json v3, server-side rendering (SSR), and Transients API caching."
date: 2026-09-03
category: "WordPress"
imageUrl: "/assets/images/blog/crear-bloques-gutenberg-consumo-api-rest.webp"
imageAlt: "WordPress Gutenberg dynamic block development workspace showing React source code, attribute inspectors, and remote REST API telemetry integration."
readTime: "11 min read"
author: "DoneAPI Engineering Team"
tags: ["WordPress", "Gutenberg", "React", "REST API", "Dynamic Blocks", "Server-Side Rendering", "Frontend"]
lang: "en"
translationSlug: "crear-bloques-gutenberg-consumo-api-rest"
featured: false
---

The WordPress block editor (**Gutenberg**) revolutionized web content authoring, replacing the legacy TinyMCE text area and bloated, shortcode-ridden page builders with modern modular architecture. Built entirely on **React**, Gutenberg allows editorial teams to compose complex digital experiences through declarative component interfaces and modular semantic structures.

However, when an organization needs to display real-time external data within articles—such as live currency exchange rates, financial ticker feeds, ERP warehouse inventory levels, or official bank holiday schedules—developers frequently make a catastrophic architectural error: **persisting volatile external API responses statically inside the WordPress database**.

If a custom block serializes the raw JSON response of a remote API directly into the rendered HTML of `post_content`, that data is permanently frozen in time. To ensure content stays continuously synchronized whenever remote sources update, software engineers must build **Dynamic Blocks with Server-Side Rendering (SSR)**.

In this deep-dive tutorial for WordPress software engineers and front-end architects, we will build a dynamic Gutenberg block from scratch using `@wordpress/scripts`, explore React’s lifecycle inside the block editor (`edit.js`), implement PHP server-side rendering (`render.php`), and shield site performance using the WordPress Transients API.

---

## 1. Static Blocks vs. Dynamic Blocks: The Architectural Fork

Gutenberg provides two distinct persistence and rendering models for custom blocks:

| Architectural Metric | Static Block | Dynamic Block (SSR) |
| :--- | :--- | :--- |
| **Persistence Mechanism** | Serializes final rendered HTML markup directly into `post_content` in `wp_posts`. | Persists only **block attributes** within a JSON boundary comment (`<!-- wp:my-plugin/live-rates {"currency":"USD"} /-->`). |
| **Frontend Rendering** | Served directly from MySQL as static HTML with zero PHP execution at runtime. | WordPress executes a server-side PHP renderer (`render.php` or `render_callback`) per request. |
| **Primary Use Cases** | Editorial typography, static hero banners, feature grids, testimonial cards. | **External REST API consumers**, dynamic product grids, live financial rates, personalized carts. |
| **Markup Invalidation Risk** | Altering HTML in `save()` triggers the dreaded *"This block contains unexpected or invalid content"* error. | **Zero markup invalidation risk**: `save()` returns `null`, and HTML compiles dynamically on the server. |

> 💡 **Golden Rule:** If a block relies on external data feeds that mutate without manual editor intervention in the WordPress dashboard, the block **MUST be dynamic**. The React `save` component must return `null`.

---

## 2. Setting Up the Development Toolchain with `@wordpress/scripts`

The official standard for modern block engineering is `@wordpress/scripts`, which encapsulates Webpack, Babel, PostCSS, and ESLint without requiring manual configuration:

```bash
# Scaffold the block project inside wp-content/plugins/
npx @wordpress/create-block doneapi-live-rates \
  --template @wordpress/create-block/template-esnext \
  --no-plugin
```

### Resulting Plugin File Tree:
```text
doneapi-live-rates/
├── block.json          # Block metadata schema (Version 3)
├── src/
│   ├── edit.js         # React component executed inside the Block Editor
│   ├── index.js        # Client-side registration entrypoint
│   ├── render.php      # Server-side PHP dynamic rendering template
│   ├── style.scss      # Shared styles across frontend and backend
│   └── editor.scss     # Admin-only block editor interface styles
├── build/              # Minified production assets compiled by Webpack
└── doneapi-plugin.php  # Main PHP bootstrap plugin file
```

---

## 3. Defining the Block Manifest: `block.json` (Schema v3)

The `block.json` file serves as the definitive single source of truth for the block. It declares attributes, script handles, and dependencies, enabling WordPress to load assets on-demand:

```json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "doneapi/live-rates",
  "version": "1.0.0",
  "title": "DoneAPI Live Exchange Rates",
  "category": "widgets",
  "icon": "chart-line",
  "description": "Displays live foreign exchange rates fetched from DoneAPI microservices.",
  "attributes": {
    "baseCurrency": {
      "type": "string",
      "default": "USD"
    },
    "targetCurrency": {
      "type": "string",
      "default": "COP"
    }
  },
  "supports": {
    "html": false,
    "align": ["wide", "full"]
  },
  "textdomain": "doneapi-rates",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "render": "file:./render.php"
}
```

---

## 4. Crafting the React Editor Experience: `edit.js`

In `edit.js`, we construct the interactive visual interface displayed to editors inside Gutenberg, complete with sidebar controls in the Block Inspector:

```jsx
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl, Spinner } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

export default function Edit({ attributes, setAttributes }) {
  const { baseCurrency, targetCurrency } = attributes;
  const blockProps = useBlockProps({ className: 'doneapi-rates-preview-box' });

  const [ratePreview, setRatePreview] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    let isMounted = true;
    setIsLoading(true);

    // Fetch live rate for editor preview
    fetch(`https://api.doneapi.com/v1/rates?base=${baseCurrency}&target=${targetCurrency}`)
      .then((res) => res.json())
      .then((data) => {
        if (isMounted) {
          setRatePreview(data.rate);
          setIsLoading(false);
        }
      })
      .catch(() => {
        if (isMounted) setIsLoading(false);
      });

    return () => {
      isMounted = false;
    };
  }, [baseCurrency, targetCurrency]);

  return (
    <>
      <InspectorControls>
        <PanelBody title={__('Exchange Settings', 'doneapi-rates')} initialOpen={true}>
          <SelectControl
            label={__('Base Currency', 'doneapi-rates')}
            value={baseCurrency}
            options={[
              { label: 'USD (US Dollar)', value: 'USD' },
              { label: 'EUR (Euro)', value: 'EUR' },
            ]}
            onChange={(val) => setAttributes({ baseCurrency: val })}
          />
          <SelectControl
            label={__('Target Currency', 'doneapi-rates')}
            value={targetCurrency}
            options={[
              { label: 'COP (Colombian Peso)', value: 'COP' },
              { label: 'MXN (Mexican Peso)', value: 'MXN' },
              { label: 'BRL (Brazilian Real)', value: 'BRL' },
            ]}
            onChange={(val) => setAttributes({ targetCurrency: val })}
          />
        </PanelBody>
      </InspectorControls>

      <div {...blockProps}>
        <div className="doneapi-rates-header">
          <h4>{__('DoneAPI Financial Telemetry [Live Preview]', 'doneapi-rates')}</h4>
        </div>
        <div className="doneapi-rates-body">
          {isLoading ? (
            <Spinner />
          ) : (
            <p>
              1 {baseCurrency} = <strong>${ratePreview?.toLocaleString() || '---'} {targetCurrency}</strong>
            </p>
          )}
        </div>
        <small className="doneapi-rates-note">
          {__('Rendered dynamically via server-side PHP on the frontend.', 'doneapi-rates')}
        </small>
      </div>
    </>
  );
}
```

And in `src/index.js`, we register the block with a stateless `save` routine:

```javascript
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import metadata from './block.json';

registerBlockType(metadata.name, {
  edit: Edit,
  save: () => null, // Dynamic blocks render strictly server-side
});
```

---

## 5. Server-Side Rendering (SSR) & Transients Caching: `render.php`

When a site visitor requests a post containing our dynamic block, WordPress executes `render.php`. If 10,000 visitors arrive concurrently, executing 10,000 outbound HTTP requests to the third-party API will exhaust server workers and trigger strict API rate limit bans.

The correct architectural solution is caching remote payloads in memory using the **WordPress Transients API**:

```php
<?php
/**
 * Server-Side Render Template
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Block inner content.
 * @var WP_Block $block      Block instance.
 */

$base_currency   = sanitize_text_field($attributes['baseCurrency'] ?? 'USD');
$target_currency = sanitize_text_field($attributes['targetCurrency'] ?? 'COP');

$cache_key = 'doneapi_rate_' . md5($base_currency . '_' . $target_currency);
$cached_data = get_transient($cache_key);

if ($cached_data === false) {
    // Cache miss: execute defensive remote HTTP fetch
    $api_url  = add_query_arg([
        'base'   => $base_currency,
        'target' => $target_currency,
    ], 'https://api.doneapi.com/v1/rates');

    $response = wp_remote_get($api_url, [
        'timeout' => 3, // Prevent slow third-party nodes from locking PHP-FPM
        'headers' => [
            'Accept' => 'application/json',
        ],
    ]);

    if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (isset($body['rate'])) {
            $cached_data = [
                'rate'      => (float) $body['rate'],
                'timestamp' => current_time('mysql'),
            ];
            // Store transient in Redis/DB for 15 minutes (900 seconds)
            set_transient($cache_key, $cached_data, 900);
        }
    }
}

$wrapper_attributes = get_block_wrapper_attributes(['class' => 'doneapi-rates-widget']);
?>

<div <?php echo $wrapper_attributes; ?>>
    <div class="doneapi-rates-card">
        <header class="doneapi-rates-title">
            <span><?php esc_html_e('Financial Exchange Rate', 'doneapi-rates'); ?></span>
        </header>
        <?php if (!empty($cached_data)) : ?>
            <div class="doneapi-rates-content">
                <div class="doneapi-rate-display">
                    <span class="doneapi-currency-code"><?php echo esc_html($base_currency . ' / ' . $target_currency); ?></span>
                    <span class="doneapi-rate-number">$<?php echo number_format($cached_data['rate'], 2); ?></span>
                </div>
                <footer class="doneapi-rates-footer">
                    <small><?php echo esc_html__('Updated:', 'doneapi-rates') . ' ' . esc_html($cached_data['timestamp']); ?></small>
                </footer>
            </div>
        <?php else : ?>
            <div class="doneapi-rates-fallback">
                <p><?php esc_html_e('Exchange rate telemetry is temporarily unavailable.', 'doneapi-rates'); ?></p>
            </div>
        <?php endif; ?>
    </div>
</div>
```

---

## 6. Registering the Block in the Core Plugin

To register the block with automatic asset resolution and translation support, register the build directory in your main plugin file:

```php
<?php
/**
 * Plugin Name: DoneAPI Gutenberg Blocks
 * Description: Enterprise dynamic block suite connected to cloud microservices.
 * Version: 1.0.0
 * Author: DoneAPI Engineering Team
 */

if (!defined('ABSPATH')) {
    exit;
}

add_action('init', function () {
    // Automatically registers block.json metadata, scripts, styles, and render.php
    register_block_type(__DIR__ . '/build');
});
```

---

## 7. WordPress Engineering & API Consulting with DoneAPI

Building bespoke Gutenberg blocks and high-scale WordPress architectures requires mastery of modern JavaScript (React, JSX, Redux state stores) paired with rock-solid PHP backend engineering (transient caching, concurrency guards, and sub-millisecond query execution).

At **DoneAPI**, we help digital media publishers, financial platforms, and e-commerce leaders across the Americas:

- **Build Advanced Gutenberg Block Libraries:** Designing bespoke block systems tailored strictly to your corporate design tokens.
- **Microservices & API Integrations:** Connecting WordPress with ERPs (SAP, NetSuite), CRMs (Salesforce, HubSpot), and real-time billing gateways.
- **Speed & Core Web Vitals Audits:** Optimizing high-traffic WordPress websites to maximize performance scores across LCP, INP, and CLS.
- **Turnkey Production Plugins:** Deploy battle-tested solutions like our **VikBooking + Mercado Pago ($7 USD)** gateway for hospitality reservation engines.

> 💬 **Need to construct dynamic Gutenberg blocks or integrate WordPress with external microservices at scale?** Connect directly with our senior software engineers on WhatsApp for technical advisory.

<div class="my-8 p-6 bg-slate-900 border border-violet-500/30 rounded-2xl shadow-xl flex flex-col md:flex-row items-center justify-between gap-6">
  <div>
    <h3 class="text-xl font-bold text-white mb-2">Build Dynamic Gutenberg Blocks & Custom Plugins with DoneAPI</h3>
    <p class="text-slate-300 text-sm max-w-xl">Harness the full power of React inside WordPress without sacrificing backend server speed or stability.</p>
  </div>
  <a href="https://wa.me/573208173939?text=Hello%20DoneAPI,%20I%20would%20like%20to%20request%20advisory%20to%20build%20dynamic%20Gutenberg%20blocks%20in%20WordPress." target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 px-6 py-3.5 bg-violet-500 hover:bg-violet-400 text-slate-950 font-bold rounded-xl transition-all shadow-lg hover:shadow-violet-500/25 shrink-0 text-sm">
    <svg class="w-5 h-5 fill-current" viewBox="0 0 24 24"><path d="M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.587-5.946.003-6.556 5.338-11.891 11.893-11.891 3.181.001 6.167 1.24 8.413 3.488 2.245 2.248 3.481 5.236 3.48 8.414-.003 6.557-5.338 11.892-11.893 11.892-1.99-.001-3.951-.5-5.688-1.448l-6.305 1.654zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.263.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.695.248-1.29.173-1.414z"/></svg>
    Speak with a Senior WordPress Engineer on WhatsApp
  </a>
</div>

---

## 8. Conclusion

The Gutenberg block editor is far more than an editorial canvas: it is a full-fledged client-side React runtime natively coupled to the WordPress backend.

Mastering **dynamic blocks with server-side rendering and Transients API caching** empowers your engineering team to enrich digital publications with real-time REST API data while guaranteeing sub-second page loads and shielding upstream services from unexpected traffic surges.
