В 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-сайтов. Начинайте с простых проектов, документируйте каждый шаг, тестируйте тщательно. Первая миграция займет недели, десятая — несколько дней. Это инвестиция в будущее, которая окупается снижением времени поддержки и улучшением пользовательского опыта для клиентов.