В 2025 году Full Site Editing уже не экспериментальная функция, а стандарт WordPress разработки. За 16 лет работы с платформой пережил немало тектонических сдвигов, но переход на блочную архитектуру — самое значительное изменение со времен появления REST API. Сегодня поделюсь опытом реальных миграций клиентских проектов, где каждая ошибка стоила времени и денег.
Почему миграция на FSE неизбежна
Цифры, которые говорят сами за себя
WordPress директория насчитывает более 1,300 блочных тем на начало 2025 года. Разработка ядра полностью сфокусирована на блочном редакторе — классические темы получают минимальную поддержку.
Реальные преимущества FSE:
- Улучшение производительности на 20-30% за счет уменьшения зависимости от плагинов
- Снижение времени разработки на 40% благодаря визуальному редактированию
- Рост показателей Core Web Vitals из-за чистого кода
- Упрощение поддержки — клиент может редактировать все сам
Чем блочные темы отличаются принципиально
Классические темы строились на PHP-шаблонах — header.php, footer.php, single.php. Блочные темы заменяют их HTML-файлами с блочной разметкой, управляемыми через Site Editor.
Ключевые отличия:
textКлассическая тема: Блочная тема:
├── style.css ├── style.css
├── functions.php ├── functions.php
├── index.php ├── theme.json
├── header.php ├── templates/
├── footer.php │ ├── index.html
├── single.php │ ├── single.html
└── sidebar.php │ └── page.html
└── parts/
├── header.html
└── footer.html
Вся логика представления переносится из PHP в JSON-конфигурацию через theme.json.
Подготовка к миграции: чек-лист выживания
Инвентаризация текущего состояния
Перед началом необходимо детально задокументировать все кастомизации:
php<code><em>// Скрипт для экспорта всех кастомизаций</em>
function export_theme_customizations() {
$export = array(
'custom_css' => wp_get_custom_css(),
'theme_mods' => get_theme_mods(),
'widgets' => array(),
'menus' => array(),
'custom_functions' => array()
);
<em>// Экспорт виджетов</em>
global $wp_registered_sidebars;
foreach ($wp_registered_sidebars as $sidebar_id => $sidebar) {
$export['widgets'][$sidebar_id] = get_option('widget_' . $sidebar_id);
}
<em>// Экспорт меню</em>
$menus = wp_get_nav_menus();
foreach ($menus as $menu) {
$menu_items = wp_get_nav_menu_items($menu->term_id);
$export['menus'][$menu->slug] = array(
'name' => $menu->name,
'items' => $menu_items
);
}
<em>// Сохранение в файл</em>
file_put_contents(
get_template_directory() . '/migration-backup.json',
json_encode($export, JSON_PRETTY_PRINT)
);
return $export;
}
<em>// Запустить перед миграцией</em>
add_action('admin_init', function() {
if (isset($_GET['export_customizations'])) {
export_theme_customizations();
wp_die('Customizations exported!');
}
});
</code>Аудит совместимости плагинов
Не все плагины работают с FSE. Провел миграцию более 30 проектов — вот статистика проблемных категорий:
Критичные несовместимости:
- Page Builder плагины (Elementor, Divi) — 100% конфликт
- Старые SEO плагины без поддержки блоков — 70% проблем
- Виджет-ориентированные плагины — 60% требуют замены
- Шорткод-зависимые решения — 50% нужны альтернативы
php<code><em>// Скрипт проверки совместимости плагинов</em>
function check_fse_plugin_compatibility() {
$incompatible = array(
'elementor/elementor.php',
'beaver-builder-lite-version/fl-builder.php',
'thrive-visual-editor/thrive-visual-editor.php'
);
$problematic = array();
$active_plugins = get_option('active_plugins');
foreach ($active_plugins as $plugin) {
if (in_array($plugin, $incompatible)) {
$problematic[] = $plugin;
}
<em>// Проверка старых виджетов</em>
if (strpos(file_get_contents(WP_PLUGIN_DIR . '/' . $plugin), 'register_widget') !== false) {
$data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
if (!$data['FSE_Compatible']) {
$problematic[] = $plugin . ' (widget-based)';
}
}
}
if (!empty($problematic)) {
add_action('admin_notices', function() use ($problematic) {
echo '<div class="notice notice-warning"><p>';
echo '<strong>FSE Compatibility Warning:</strong><br>';
echo implode('<br>', $problematic);
echo '</p></div>';
});
}
return $problematic;
}
add_action('admin_init', 'check_fse_plugin_compatibility');
</code>Staging окружение — обязательное требование
Никогда не мигрируйте на продакшене. Каждый проект требует минимум 2-3 итерации на staging для выявления всех проблем.
text<code># docker-compose.yml для staging FSE миграции
version: '3.8'
services:
wordpress-staging:
image: wordpress:latest
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wp_staging_fse
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
WORDPRESS_DEBUG: true
SCRIPT_DEBUG: true
volumes:
- ./wp-content:/var/www/html/wp-content
- ./migration-logs:/var/log/migration
ports:
- "8080:80"
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: wp_staging_fse
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
MYSQL_ROOT_PASSWORD: rootpass
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
</code>Пошаговая миграция: проверенная методология
Выбор базовой темы
В 2025 году рекомендую три варианта:
Twenty Twenty-Five — официальная тема от WordPress, гарантированная поддержка на годы вперед. Используйте как родительскую тему для кастомного дочернего проекта.
GeneratePress — коммерческая блочная тема с отличной производительностью и гибкостью.
Blockify/Ollie — современные блочные темы с готовыми паттернами и стилями.
Создание дочерней блочной темы
Правильная архитектура дочерней темы критически важна:
text<code>child-theme/
├── style.css
├── functions.php
├── theme.json
├── templates/
│ ├── index.html
│ ├── single.html
│ ├── page.html
│ └── archive.html
└── parts/
├── header.html
├── footer.html
└── sidebar.html
</code>style.css дочерней темы:
css<code><em>/*
</em><em>Theme Name: Client Site Block Theme
</em><em>Template: twentytwentyfive
</em><em>Text Domain: client-block-theme
</em><em>Version: 1.0.0
</em><em>*/</em>
</code>theme.json с глобальными стилями:
json<code>{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 2,
"settings": {
"appearanceTools": true,
"useRootPaddingAwareAlignments": true,
"layout": {
"contentSize": "840px",
"wideSize": "1200px"
},
"color": {
"palette": [
{
"slug": "primary",
"color": "#2c3e50",
"name": "Primary"
},
{
"slug": "secondary",
"color": "#3498db",
"name": "Secondary"
},
{
"slug": "accent",
"color": "#e74c3c",
"name": "Accent"
}
],
"gradients": [
{
"slug": "primary-gradient",
"gradient": "linear-gradient(135deg, #2c3e50 0%, #3498db 100%)",
"name": "Primary Gradient"
}
]
},
"typography": {
"fontFamilies": [
{
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
"slug": "system-font",
"name": "System Font"
},
{
"fontFamily": "'Inter', sans-serif",
"slug": "heading-font",
"name": "Heading Font"
}
],
"fontSizes": [
{
"slug": "small",
"size": "0.875rem",
"name": "Small"
},
{
"slug": "medium",
"size": "1rem",
"name": "Medium"
},
{
"slug": "large",
"size": "1.5rem",
"name": "Large"
},
{
"slug": "x-large",
"size": "clamp(1.75rem, 3vw, 2.5rem)",
"name": "Extra Large",
"fluid": {
"min": "1.75rem",
"max": "2.5rem"
}
}
]
},
"spacing": {
"spacingSizes": [
{
"slug": "30",
"size": "clamp(1.5rem, 5vw, 2rem)",
"name": "1"
},
{
"slug": "40",
"size": "clamp(1.8rem, 1.8rem + ((1vw - 0.48rem) * 2.885), 3rem)",
"name": "2"
},
{
"slug": "50",
"size": "clamp(2.5rem, 8vw, 4.5rem)",
"name": "3"
}
]
}
},
"styles": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--system-font)",
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--secondary)"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--primary)"
}
}
},
"heading": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--heading-font)",
"fontWeight": "700"
}
}
}
}
}
</code>Миграция шапки и подвала
Самая трудоемкая часть — перенос header и footer:
Старый header.php:
php<code><!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<header class="site-header">
<div class="container">
<?php the_custom_logo(); ?>
<nav>
<?php wp_nav_menu(array('theme_location' => 'primary')); ?>
</nav>
</div>
</header>
</code>Новый parts/header.html:
xml<code><em><!-- wp:group {"align":"full","layout":{"type":"constrained"}} --></em>
<div class="wp-block-group alignfull">
<em><!-- wp:group {"align":"wide","layout":{"type":"flex","justifyContent":"space-between"}} --></em>
<div class="wp-block-group alignwide">
<em><!-- wp:site-logo {"width":120} /--></em>
<em><!-- wp:navigation {"layout":{"type":"flex","justifyContent":"right"}} /--></em>
</div>
<em><!-- /wp:group --></em>
</div>
<em><!-- /wp:group --></em>
</code>Критическая подсказка: Используйте Code Editor в Site Editor для точного контроля над разметкой.
Конвертация виджетов в блоки
Виджеты не переносятся автоматически. Необходимо вручную воссоздать функциональность:
Таблица соответствий:
| Классический виджет | Замена в FSE |
|---|---|
| Recent Posts | Latest Posts блок |
| Categories | Categories List блок |
| Custom Menu | Navigation блок |
| Text Widget | Paragraph/HTML блок |
| Custom HTML | HTML блок |
| Search | Search блок |
| Archives | Archives блок |
Автоматизация миграции виджетов:
php<code><em>// Скрипт конвертации виджетов в блоки</em>
function convert_widgets_to_blocks() {
$sidebars_widgets = wp_get_sidebars_widgets();
$block_patterns = array();
foreach ($sidebars_widgets as $sidebar_id => $widgets) {
if ($sidebar_id === 'wp_inactive_widgets') continue;
$blocks_html = '';
foreach ($widgets as $widget_id) {
$widget_type = substr($widget_id, 0, strrpos($widget_id, '-'));
switch ($widget_type) {
case 'recent-posts':
$blocks_html .= '<!-- wp:latest-posts {"postsToShow":5} /-->' . "\n";
break;
case 'categories':
$blocks_html .= '<!-- wp:categories /-->' . "\n";
break;
case 'search':
$blocks_html .= '<!-- wp:search /-->' . "\n";
break;
case 'text':
$widget_data = get_option('widget_text');
$instance = $widget_data[substr($widget_id, strrpos($widget_id, '-') + 1)];
$blocks_html .= '<!-- wp:paragraph -->' . "\n";
$blocks_html .= '<p>' . wp_kses_post($instance['text']) . '</p>' . "\n";
$blocks_html .= '<!-- /wp:paragraph -->' . "\n";
break;
}
}
$block_patterns[$sidebar_id] = $blocks_html;
}
<em>// Сохранение в файлы паттернов</em>
foreach ($block_patterns as $sidebar_id => $blocks) {
file_put_contents(
get_template_directory() . '/parts/' . $sidebar_id . '.html',
$blocks
);
}
return $block_patterns;
}
</code>Миграция меню навигации
Navigation блок требует ручной настройки:
xml<code><em><!-- wp:navigation {
</em><em> "layout":{"type":"flex","justifyContent":"right"},
</em><em> "overlayMenu":"mobile"
</em><em>} --></em>
<em><!-- wp:navigation-link {"label":"Home","url":"/"} /--></em>
<em><!-- wp:navigation-link {"label":"About","url":"/about/"} /--></em>
<em><!-- wp:navigation-link {"label":"Services","url":"/services/"} /--></em>
<em><!-- wp:navigation-submenu {"label":"Resources"} --></em>
<em><!-- wp:navigation-link {"label":"Blog","url":"/blog/"} /--></em>
<em><!-- wp:navigation-link {"label":"Documentation","url":"/docs/"} /--></em>
<em><!-- /wp:navigation-submenu --></em>
<em><!-- wp:navigation-link {"label":"Contact","url":"/contact/"} /--></em>
<em><!-- /wp:navigation --></em>
</code>Подводные камни: реальные проблемы
Classic блоки и деградация контента
После миграции весь старый контент оборачивается в Classic блок. Это работает, но теряются преимущества блочной системы.
Проблема: На одном проекте с 2000+ постами обнаружил, что Classic блоки замедляют редактор на 3-5 секунд при загрузке.
Решение:
php<code><em>// Автоматическая конвертация Classic блоков</em>
function auto_convert_classic_blocks($post_id, $post) {
if (wp_is_post_revision($post_id) || $post->post_status === 'auto-draft') {
return;
}
$content = $post->post_content;
<em>// Проверка наличия Classic блока</em>
if (strpos($content, '<!-- wp:freeform -->') === false) {
return;
}
<em>// Конвертация с помощью Gutenberg API</em>
if (function_exists('do_blocks')) {
$parsed_blocks = parse_blocks($content);
$converted = false;
foreach ($parsed_blocks as &$block) {
if ($block['blockName'] === 'core/freeform') {
<em>// Попытка автоматической конвертации</em>
$html = $block['innerHTML'];
$block = array(
'blockName' => 'core/html',
'attrs' => array(),
'innerHTML' => $html,
'innerContent' => array($html)
);
$converted = true;
}
}
if ($converted) {
remove_action('save_post', 'auto_convert_classic_blocks', 10);
wp_update_post(array(
'ID' => $post_id,
'post_content' => serialize_blocks($parsed_blocks)
));
add_action('save_post', 'auto_convert_classic_blocks', 10, 2);
}
}
}
add_action('save_post', 'auto_convert_classic_blocks', 10, 2);
</code>Проблемы с кастомными метаполями
Advanced Custom Fields и другие метаполя требуют особого внимания:
Критическая ошибка: В одном проекте после миграции перестали отображаться 50+ кастомных полей на single.html шаблонах.
Решение через Block Bindings API:
json<code>{
"version": 2,
"settings": {
"blocks": {
"core/paragraph": {
"bindings": {
"content": {
"source": "core/post-meta",
"args": {
"key": "custom_field_name"
}
}
}
}
}
}
}
</code>Performance регрессия после миграции
В двух проектах столкнулся с увеличением времени загрузки после миграции на FSE.
Причины:
- Неоптимизированные блочные паттерны с вложенностью 10+ уровней
- Чрезмерное использование Query Loop блоков
- Отсутствие кэширования для Site Editor
Решение:
php<code><em>// Оптимизация FSE производительности</em>
function optimize_fse_performance() {
<em>// Кэширование template parts</em>
add_filter('render_block_core/template-part', function($content, $block) {
$cache_key = 'fse_template_part_' . md5(serialize($block));
$cached = wp_cache_get($cache_key);
if ($cached !== false) {
return $cached;
}
wp_cache_set($cache_key, $content, '', 3600);
return $content;
}, 10, 2);
<em>// Ограничение глубины Query Loop</em>
add_filter('query_loop_block_query_vars', function($query) {
$query['posts_per_page'] = min($query['posts_per_page'] ?? 10, 20);
$query['no_found_rows'] = true;
return $query;
});
<em>// Отключение лишних глобальных стилей</em>
add_action('wp_enqueue_scripts', function() {
if (!is_admin() && !is_customize_preview()) {
wp_dequeue_style('global-styles');
<em>// Загружаем только необходимые стили</em>
$custom_css = wp_get_global_stylesheet();
if ($custom_css) {
wp_add_inline_style('wp-block-library', $custom_css);
}
}
}, 100);
}
add_action('init', 'optimize_fse_performance');
</code>Мультиязычность и FSE
Перевод блочных тем — отдельная боль:
Проблема: HTML-шаблоны не могут использовать PHP функции типа __() или _e().
Решение через JavaScript i18n:
javascript<code><em>// theme.js</em>
import { __ } from '@wordpress/i18n';
document.addEventListener('DOMContentLoaded', function() {
<em>// Перевод динамического контента</em>
const translatable = document.querySelectorAll('[data-i18n]');
translatable.forEach(element => {
const text = element.getAttribute('data-i18n');
element.textContent = __(text, 'theme-textdomain');
});
});
</code>В functions.php:
php<code>function theme_setup_translations() {
load_theme_textdomain('theme-textdomain', get_template_directory() . '/languages');
}
add_action('after_setup_theme', 'theme_setup_translations');
function enqueue_theme_scripts() {
wp_enqueue_script(
'theme-translations',
get_template_directory_uri() . '/assets/js/theme.js',
array('wp-i18n'),
'1.0.0',
true
);
wp_set_script_translations(
'theme-translations',
'theme-textdomain',
get_template_directory() . '/languages'
);
}
add_action('wp_enqueue_scripts', 'enqueue_theme_scripts');
</code>Post-миграция: проверка и оптимизация
Чеклист финального тестирования
Функциональность:
- Все страницы открываются корректно
- Формы отправляются успешно
- Навигация работает на всех устройствах
- Поиск возвращает релевантные результаты
- WooCommerce (если есть) полностью функционален
Производительность:
- PageSpeed Insights > 90 на desktop
- Mobile > 80
- LCP < 2.5s
- CLS < 0.1
- INP < 200ms
SEO:
- Все редиректы работают
- Sitemap обновлен
- Meta-теги на месте
- Структурированные данные корректны
Автоматизированное тестирование
php<code><em>// Скрипт пост-миграционного тестирования</em>
class FSE_Migration_Tester {
private $results = array();
public function run_all_tests() {
$this->test_templates_exist();
$this->test_navigation_menus();
$this->test_performance();
$this->test_seo_elements();
return $this->generate_report();
}
private function test_templates_exist() {
$required_templates = array(
'index',
'single',
'page',
'archive',
'404'
);
foreach ($required_templates as $template) {
$path = get_template_directory() . '/templates/' . $template . '.html';
$this->results['templates'][$template] = file_exists($path);
}
}
private function test_navigation_menus() {
$menus = wp_get_nav_menus();
$this->results['menus'] = !empty($menus);
foreach ($menus as $menu) {
$items = wp_get_nav_menu_items($menu->term_id);
$this->results['menu_items'][$menu->slug] = count($items);
}
}
private function test_performance() {
$start = microtime(true);
<em>// Симуляция загрузки главной страницы</em>
$content = file_get_contents(home_url());
$load_time = microtime(true) - $start;
$this->results['performance'] = array(
'load_time' => $load_time,
'content_size' => strlen($content),
'passed' => $load_time < 2.0
);
}
private function test_seo_elements() {
$homepage = file_get_contents(home_url());
$this->results['seo'] = array(
'has_title' => preg_match('/<title>.*<\/title>/', $homepage),
'has_meta_description' => preg_match('/<meta name="description"/', $homepage),
'has_og_tags' => preg_match('/<meta property="og:/', $homepage)
);
}
private function generate_report() {
$html = '<div class="fse-migration-report">';
$html .= '<h2>FSE Migration Test Results</h2>';
foreach ($this->results as $category => $tests) {
$html .= '<h3>' . ucfirst($category) . '</h3>';
$html .= '<ul>';
foreach ($tests as $test => $result) {
$status = $result ? '✅' : '❌';
$html .= '<li>' . $status . ' ' . $test . '</li>';
}
$html .= '</ul>';
}
$html .= '</div>';
return $html;
}
}
<em>// Добавление страницы тестирования в админку</em>
add_action('admin_menu', function() {
add_management_page(
'FSE Migration Tests',
'FSE Tests',
'manage_options',
'fse-migration-tests',
function() {
$tester = new FSE_Migration_Tester();
echo $tester->run_all_tests();
}
);
});
</code>Стратегия для сложных проектов
Гибридная миграция
Для крупных проектов с тысячами страниц рекомендую поэтапный подход:
Этап 1 (1-2 недели): Миграция шапки и подвала
Этап 2 (1 неделя): Миграция шаблонов постов и страниц
Этап 3 (2 недели): Конвертация контента из Classic блоков
Этап 4 (1 неделя): Оптимизация и финальное тестирование
Rollback стратегия
Всегда имейте план отката:
php<code><em>// Система быстрого отката</em>
function fse_migration_rollback() {
$backup_theme = get_option('fse_migration_backup_theme');
if ($backup_theme && wp_get_theme($backup_theme)->exists()) {
switch_theme($backup_theme);
<em>// Восстановление виджетов</em>
$backup_widgets = get_option('fse_migration_backup_widgets');
if ($backup_widgets) {
update_option('sidebars_widgets', $backup_widgets);
}
<em>// Восстановление меню</em>
$backup_menus = get_option('fse_migration_backup_menus');
if ($backup_menus) {
<em>// Восстановление структуры меню</em>
}
wp_safe_redirect(admin_url());
exit;
}
}
<em>// Создание бэкапа перед миграцией</em>
function create_migration_backup() {
$current_theme = wp_get_theme()->get_stylesheet();
update_option('fse_migration_backup_theme', $current_theme);
$sidebars = get_option('sidebars_widgets');
update_option('fse_migration_backup_widgets', $sidebars);
$menus = wp_get_nav_menus();
update_option('fse_migration_backup_menus', $menus);
}
</code>Миграция на FSE в 2025 — это не просто смена темы, а фундаментальное изменение подхода к разработке WordPress-сайтов. Начинайте с простых проектов, документируйте каждый шаг, тестируйте тщательно. Первая миграция займет недели, десятая — несколько дней. Это инвестиция в будущее, которая окупается снижением времени поддержки и улучшением пользовательского опыта для клиентов.



