How to Implement Server-Side Rendering for WordPress Blocks?

Recently, I was tasked with implementing server-side rendering for a WordPress block created with @wordpress/create-block package. Specifically, I developed a block that displays a page list based on a user-selected menu.

The block’s functionality is straightforward: in the back-end, the user selects a menu from a list of available menus, and on the front-end, the selected menu is displayed dynamically as standard wordpress menu.

While working on this, I experimented with several approaches before realizing that this functionality has been natively supported in WordPress since version 6.1 (release on November 1, 2022). Here’s the relevant code from the WordPress source:

	if ( ! empty( $metadata['render'] ) ) {
		$template_path = wp_normalize_path(
			realpath(
				dirname( $metadata['file'] ) . '/' .
				remove_block_asset_path_prefix( $metadata['render'] )
			)
		);
		if ( $template_path ) {
			/**
			 * Renders the block on the server.
			 *
			 * @since 6.1.0
			 *
			 * @param array    $attributes Block attributes.
			 * @param string   $content    Block default content.
			 * @param WP_Block $block      Block instance.
			 *
			 * @return string Returns the block content.
			 */
			$settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) {
				ob_start();
				require $template_path;
				return ob_get_clean();
			};
		}
	}

This snippet is from the register_block_type_from_metadata function, which handles parsing the block.json definition. To define a custom PHP renderer, simply include it in your block definition like this:

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "theme/menu-items-list",
	"version": "0.1.0",
	"title": "Menu Items List",
	"category": "widgets",
	"attributes": {
		"menuId": {
			"type": "string",
			"default": "0"
		}
	},
	"icon": "smiley",
	"description": "Display menu list.",
	"example": {},
	"supports": {
		"html": false
	},
	"textdomain": "menu-items-list",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js",
	"render": "file:./render.php"
}

Place the render.php file in the same directory as your block.json file.

The ./render.php file will be rendered on the user-facing front end (not in the admin interface). In this file, you also have access to the $attributes, $content, and $block variables, allowing you to customize the output dynamically based on block settings and content.

I think that is pretty neat feature for WordPress developers.

Here are my contents for the PHP file:

<?php

$menu_id = $attributes['menuId'] ?? null;

$menu_items = wp_get_nav_menu_items($menu_id);

if (!$menu_id || !$menu_items) {
    if (!$menu_items) {
        echo '<p>' . esc_html__('No menu selected.', 'menu-items-list') . '</p>';
    } else if (!$menu_items) {
        echo '<p>' . esc_html__('No menu items found for the selected menu.', 'menu-items-list') . '</p>';
    }
} else {

    wp_nav_menu(array(
        'menu' => $menu_id, // Replace '3' with the ID of your menu
        'theme_location' => '', // Leave empty if using 'menu' ID
        'container' => 'div', // The container HTML element (default: 'div')
        'container_class' => 'menu-container', // Class for the container
        'menu_class' => 'menu', // Class for the ul element
        'fallback_cb' => false // Callback if the menu does not exist
    ));
}