Привет! Меня зовут Артём, я бэкенд-разработчик. Когда в WordPress 5.0 появился Gutenberg, я был в ужасе — «что значит, мне нужен React для создания блока в WordPress?!». Пять лет спустя ситуация улучшилась, но большинство туториалов всё ещё начинают с npx @wordpress/create-block и требуют настройку сборки через webpack.
Однако в WordPress 5.9 появилась возможность создавать так называемые «server-side blocks» — блоки, которые полностью рендерятся на PHP, без единой строки JavaScript. Для backend-разработчика это настоящий глоток свежего воздуха.
Что такое серверный блок?
Серверный блок (Server-Side Rendered Block) — это блок, чей HTML генерируется на сервере при сохранении поста и при рендере страницы. В редакторе он отображается как статичный HTML (не интерактивный), а в базе данных хранится в виде сериализованного block comment.
Это значит: вы пишете PHP-функцию, которая принимает атрибуты блока и возвращает HTML. WordPress оборачивает результат в <!-- wp:my-plugin/awesome-block --> и сохраняет в post_content.
Минимальный пример
Вот полный код серверного блока, который выводит карточку с заголовком и описанием:
// Регистрация блока
add_action('init', function() {
register_block_type('my-plugin/info-card', [
'api_version' => 2,
'attributes' => [
'title' => [
'type' => 'string',
'default' => 'Информационная карточка',
],
'description' => [
'type' => 'string',
'default' => '',
],
'style' => [
'type' => 'string',
'default' => 'default',
],
],
'render_callback' => 'render_info_card_block',
]);
});
function render_info_card_block($attributes) {
$title = esc_html($attributes['title']);
$description = esc_html($attributes['description']);
$style = sanitize_html_class($attributes['style']);
ob_start();
?>
<div class="info-card info-card--<?php echo $style; ?>">
<div class="info-card__header">
<h3><?php echo $title; ?></h3>
</div>
<div class="info-card__body">
<p><?php echo $description; ?></p>
</div>
</div>
<?php
return ob_get_clean();
}
Всё. Без webpack, без React, без package.json. Один вызов register_block_type с render_callback, и блок работает.
Как это выглядит в редакторе?
Когда пользователь добавляет ваш серверный блок в редакторе, Gutenberg вызывает render_callback через REST API (эндпоинт /wp-json/wp/v2/block-renderer/my-plugin/info-card). Ответ — HTML, который отображается в редакторе как «превью» блока. Пользователь может менять атрибуты через инспектор (sidebar), и при каждом изменении Gutenberg делает новый запрос к REST API.
Это означает, что render_callback выполняется при каждом изменении атрибутов в редакторе. Если ваш callback делает тяжёлые запросы к БД или внешним API, редактор начнёт тормозить. Решение — кэшировать результат:
function render_info_card_block($attributes) {
$cache_key = 'info_card_' . md5(serialize($attributes));
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
// Тяжёлая логика
$data = fetch_expensive_data($attributes);
ob_start();
// ... рендеринг ...
$html = ob_get_clean();
set_transient($cache_key, $html, HOUR_IN_SECONDS);
return $html;
}
Динамические данные: блок с выбором из БД
Один из реальных кейсов: блок «Последние отзывы», который выводит 3 последних одобренных отзыва из кастомного post type. Мы используем rest_api_init для добавления опций в инспектор блока:
register_block_type('my-plugin/recent-reviews', [
'api_version' => 2,
'attributes' => [
'count' => [
'type' => 'number',
'default' => 3,
'minimum' => 1,
'maximum' => 20,
],
'category' => [
'type' => 'string',
'default' => 'all',
],
],
'render_callback' => 'render_recent_reviews_block',
]);
function render_recent_reviews_block($attributes) {
$count = (int) $attributes['count'];
$category = sanitize_key($attributes['category']);
$args = [
'post_type' => 'review',
'posts_per_page' => $count,
'post_status' => 'publish',
];
if ($category !== 'all') {
$args['tax_query'] = [[
'taxonomy' => 'review_category',
'field' => 'slug',
'terms' => $category,
]];
}
$reviews = get_posts($args);
if (empty($reviews)) {
return '<p>' . esc_html__('Отзывов пока нет.', 'my-plugin') . '</p>';
}
ob_start();
echo '<div class="recent-reviews">';
foreach ($reviews as $review) {
echo '<div class="review-card">';
echo '<h4>' . esc_html(get_the_title($review)) . '</h4>';
echo '<p>' . esc_html(wp_trim_words($review->post_content, 30)) . '</p>';
echo '<span class="review-author">' . esc_html(get_post_meta($review->ID, 'author_name', true)) . '</span>';
echo '</div>';
}
echo '</div>';
return ob_get_clean();
}
Обратите внимание: мы используем get_posts вместо WP_Query. Разница в производительности небольшая, но get_posts не меняет глобальные переменные WordPress ($wp_query, $post), что делает его безопаснее для использования внутри render_callback.
Стили: enqueue_block_style
WordPress 5.9 добавил функцию enqueue_block_style, которая подключает CSS только когда блок реально используется на странице. Это лучше, чем enqueue_style на wp_enqueue_scripts, который загружает стили на всех страницах.
add_action('init', function() {
register_block_type('my-plugin/info-card', [ /* ... */ ]);
enqueue_block_style('my-plugin/info-card',
plugin_dir_url(__FILE__) . 'blocks/info-card/style.css'
);
});
WordPress автоматически определяет наличие блока в post_content и подключает CSS только для страниц, где блок используется. На страницах без блока CSS не загружается — это заметно ускоряет фронтенд при большом количестве кастомных блоков.
Серверные блоки — не универсальное решение. Для сложных интерактивных блоков (слайдеры, формы, интерактивные графики) всё равно нужен React. Но для контентных блоков — карточки, списки, цитаты, баннеры — PHP-подход идеален. Быстро, чисто, без сборки.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье