Headless виправданий, коли контент споживає більше ніж один клієнт або команда вже живе в JS-екосистемі; платити за це доводиться прев'ю, редиректами, формами, CSS блоків, меню, SEO-мета й власною інвалідацією кешу — усім, що раніше безкоштовно робила тема.
Headless WordPress означає, що тема більше не рендерить сторінки: WP лишається редакторським інтерфейсом і сховищем, а HTML будує окремий застосунок, який тягне дані через REST (ядро, з WP 4.7) або WPGraphQL. Ключове рішення тут не технічне, а продуктове — скільки різних клієнтів споживає той самий контент. Якщо клієнт один і це сайт, ви обмінюєте готовий рендер на власний і майже нічого не отримуєте. Якщо клієнтів кілька (веб, мобільний застосунок, екрани в залі, партнерська стрічка), або продукт — інтерактивний застосунок, де WP лише одне з джерел даних, або команда фронтенду вже живе в React і має дизайн-систему, — розділення окупається.
Аргумент «headless заради швидкості» майже завжди хибний. Повільність типового WP створюють запити в WP_Query, autoload-опції та десяток плагінів на кожен хіт, і все це нікуди не зникає — воно просто переїжджає з рендера сторінки в рендер JSON. Різниця в тому, що перед темою повносторінковий кеш ставиться однією настройкою, а перед API його треба проєктувати: REST-відповіді залежать від автентифікації, per_page обмежений сотнею, а без _fields кожен пост тягне за собою рендерений контент, мета й _links. Перш ніж пропонувати headless, чесніше показати профіль запитів і спробувати кеш на рівні edge.
Приховані витрати — це рівно те, що безкоштовно робила тема. Прев'ю чернеток вимагає автентифікованих запитів (Application Passwords з WP 5.6 або JWT) і роботи з wp/v2/posts/{id}/autosaves, бо незбережені зміни лежать саме там. CSS блоків генерується на сервері функціями wp_get_global_stylesheet() і wp_enqueue_block_style() (обидві з WP 5.9), а wp_head() на фронтенді ніхто не викличе. Меню через wp/v2/menus анонімно не читаються: ендпоінти вимагають edit_theme_options. Редиректи плагінів застосовуються на template_redirect, якого немає. Форми, шорткоди, srcset, related posts, канонічні URL і _wp_old_slug_redirect — той самий список. Кожен пункт треба або відтворити, або свідомо викинути.
Окремо живе інвалідація. У класичному WP кеш чистило саме ядро й плагін кешування; у headless це ваш код: transition_post_status або save_post → вебхук на фронтенд, який робить revalidatePath/revalidateTag (Next.js App Router, 13.4+). Пастка не в самому вебхуку, а в повноті: зміна поста впливає на його сторінку, на архіви, на головну, на сусідні пости в «схожих», іноді на меню. Тому інвалідують не URL, а теги, і запит роблять неблокуючим чи через wp_cron, інакше кожне збереження в редакторі затримується на час відповіді фронтенду.
Є ще операційна ціна: два деплої, два середовища, два набори логів і два домени. Бекенд лишається повноцінним сайтом, який віддає ті самі тексти за власними URL, тож його закривають від індексації (X-Robots-Tag: noindex, basic auth, обмеження за IP) — інакше отримаєте дублі контенту. Для SEO треба одне джерело правди: або фронтенд рахує мета-теги сам, або бере yoast_head_json з Yoast (з 14.x) і не сперечається з ним. І окремо — команда: підтримувати headless без постійного фронтенд-розробника не вийде, а більшість «просто контентних» сайтів такого розробника не мають. Проміжні варіанти теж існують: блочна тема з theme.json для контентної частини плюс окремий SPA-розділ для інтерактивного, — і для багатьох проєктів це чесніша відповідь, ніж повний headless.
// 1. Інвалідація фронтенду: тему знято, тож кеш чистить хук, а не WP
add_action('transition_post_status', function (string $new, string $old, WP_Post $post): void {
if ($new !== 'publish' && $old !== 'publish') {
return; // чернетка → чернетка фронтенду не стосується
}
if (wp_is_post_revision($post->ID) || $post->post_type === 'nav_menu_item') {
return;
}
// HTTP-запит просто в хуку сповільнить збереження в редакторі — відкладаємо
wp_schedule_single_event(time(), 'shop_revalidate', [get_permalink($post), $post->post_type]);
}, 10, 3);
add_action('shop_revalidate', function (string $url, string $type): void {
wp_remote_post(SHOP_FRONT_URL.'/api/revalidate', [
'timeout' => 5,
'blocking' => false,
'headers' => ['X-Revalidate-Secret' => SHOP_REVALIDATE_SECRET],
// Окрім самої сторінки — архіви й головна, інакше список лишиться старим
'body' => wp_json_encode([
'path' => wp_parse_url($url, PHP_URL_PATH),
'tags' => ['home', "archive:{$type}"],
]),
]);
}, 10, 2);
// 2. CSS блоків генерує сервер: без wp_head() фронтенд його не отримає
add_action('rest_api_init', function (): void {
register_rest_route('shop/v1', '/block-styles', [
'methods' => 'GET',
'permission_callback' => '__return_true', // публічні стилі, не дані
'callback' => static fn (): array => [
// з WP 5.9: змінні й пресети з theme.json + стилі блоків
'css' => wp_get_global_stylesheet(['variables', 'presets', 'styles']),
],
]);
});
Сформулюйте критерій одним реченням: headless окупається, коли споживачів контенту більше одного (сайт + мобільний застосунок + екрани в залі) або коли продукт — це застосунок, а WP у ньому лише CMS. Для контентного сайту з блогом чесніше сказати «ні» і показати, скільки коштує відтворити прев'ю, форми й меню.