The Gutenberg editor has revolutionized how content is created in WordPress. While simple blocks can be built quickly, creating complex, dynamic blocks with React and server-side rendering (SSR) unlocks powerful capabilities — especially when dealing with dynamic data or API integrations.

In this guide, we’ll cover how to build an advanced Gutenberg block using:

  • React (JSX + Hooks)
  • @wordpress/scripts for tooling
  • register_block_type with render_callback
  • Inspector controls
  • Dynamic attributes and server rendering

Prerequisites

Before starting, make sure you have:

  • A working WordPress plugin or theme
  • Node.js and npm installed
  • @wordpress/scripts setup for building your block

You can scaffold a block using:

npx @wordpress/create-block my-dynamic-block

Project Structure

Once scaffolded, your plugin folder will look like this:

my-dynamic-block/
├── src/
│   ├── edit.js         // React editor UI
│   ├── save.js         // Can be skipped for dynamic blocks
│   └── index.js        // Block registration
├── dynamic-block.php   // Server-side rendering logic
└── block.json          // Block metadata

Why Dynamic Rendering?

Dynamic rendering allows your block to:

  • Display live data (e.g. latest posts, user info, API content)
  • Render server-side for SEO and performance
  • Avoid bloated frontend JS for static rendering

Example Use Case: Recent Posts Grid

We’ll create a custom block that:

  • Displays recent posts in a grid
  • Lets you control the number of posts in the sidebar
  • Dynamically renders via PHP on the frontend

Step 1: block.json Setup

{
  "apiVersion": 2,
  "name": "myplugin/recent-posts-grid",
  "title": "Recent Posts Grid",
  "category": "widgets",
  "icon": "grid-view",
  "supports": {
    "html": false
  },
  "attributes": {
    "postCount": {
      "type": "number",
      "default": 3
    }
  },
  "editorScript": "file:./index.js",
  "render": "file:./dynamic-block.php"
}

Step 2: React Editor UI (edit.js)

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';

export default function Edit({ attributes, setAttributes }) {
  return (
    <>
      <InspectorControls>
        <PanelBody title="Block Settings">
          <RangeControl
            label="Number of posts"
            value={attributes.postCount}
            onChange={(value) => setAttributes({ postCount: value })}
            min={1}
            max={10}
          />
        </PanelBody>
      </InspectorControls>
      <p><em>Recent posts will be shown on the frontend.</em></p>
    </>
  );
}

Step 3: Server-Side Render (dynamic-block.php)

function render_recent_posts_grid( $attributes ) {
    $query = new WP_Query([
        'posts_per_page' => intval($attributes['postCount']),
    ]);

    if ( ! $query->have_posts() ) {
        return '<p>No recent posts found.</p>';
    }

    ob_start();
    echo '<div class="recent-posts-grid">';
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<div class="post-item">';
        echo '<h4>' . esc_html(get_the_title()) . '</h4>';
        echo '<a href="' . esc_url(get_permalink()) . '">Read More</a>';
        echo '</div>';
    }
    echo '</div>';
    wp_reset_postdata();

    return ob_get_clean();
}

register_block_type( __DIR__, [
    'render_callback' => 'render_recent_posts_grid'
]);

Bonus Tips

  • Add styles using editorStyle and style in block.json
  • Use useSelect() in the editor to preview post data live
  • Use InnerBlocks to allow nested block customization
  • Use withSelect HOC for backward compatibility if needed

Final Thoughts

Creating dynamic Gutenberg blocks with React gives you the best of both worlds:

  • A modern editing experience
  • Dynamic, SEO-friendly output
  • Clean separation between editor and render logic

With this pattern, you can build anything from live charts, API feeds, product listings, or custom dashboards — all powered by WordPress.