В архітектурі Headless WordPress на базі Next.js 16 (App Router) стратегія рендерингу та керування кешем визначає баланс між продуктивністю користувацького досвіду (Time to First Byte, TTFB), навантаженням на базу даних CMS та актуальністю відображуваного контенту.
Історичний підхід Pages Router (розподіл на getStaticProps, getServerSideProps та фіксований інтервал revalidate: N) у сучасному стеку замінено на чотирирівневу систему кешування Next.js:
fetch у межах одного циклу рендерингу серверних компонентів (RSC) через React Cache.next: { tags: ['posts', 'post:42'] }) або новою директивою "use cache" із функцією cacheTag().Порівняння базових стратегій:
next build. Забезпечує мінімальний TTFB (< 30 мс) з Edge CDN, але за наявності 100 000 публікацій збірка проєкту займає години, а будь-які зміни в CMS вимагають повного перезапуску CI/CD.cache: 'no-store'). Гарантує 100% свіжість даних, але створює пряме лінійне навантаження на PHP-воркери WordPress та СУБД MySQL, призводячи до затримок відповіді 300–1200 мс.revalidate: 60). Має фундаментальну ваду: контент відстає від реального стану в CMS на задану кількість секунд, а при закінченні таймера під високим навантаженням виникає ризик каскадних повторних обчислень.Інженерний стандарт — On-Demand Tagged Invalidation (ISR за вимогою):
Сторінки кешуються на Edge CDN безстроково. У момент публікації, оновлення або видалення статті в адмінці WordPress ядро через хуки transition_post_status та post_updated надсилає криптографічно підписаний вебхук (HMAC-SHA256) у Next.js Route Handler (/api/revalidate). Обробник верифікує підпис та викликає атомарну інвалідацію конкретного тегу: revalidateTag('post:42'). Потрібна сторінка миттєво скидається з кешу CDN і перезбирається у фоновому режимі за патерном Stale-While-Revalidate, тоді як інші 99 999 сторінок залишаються недоторканими.
Для масштабування каталогів із мільйонами записів застосовується гібридна модель: функція generateStaticParams() передкомпілює лише топ-100 найбільш відвідуваних статей на етапі збірки, а прапорець export const dynamicParams = true забезпечує ліниву генерацію та кешування всіх інших сторінок при першому візиті користувача.
Архітектура миттєвої інвалідації кешу (On-Demand ISR) між CMS та Edge CDN:
+-----------------------------------------------------------------------------------------+
| АДМІНІСТРАТИВНИЙ БЕКЕНД WORDPRESS |
+-----------------------------------------------------------------------------------------+
| [Редактор натискає "Опублікувати"] |
| | |
| v |
| [Хук: transition_post_status / post_updated] |
| | |
| v |
| [HMAC-SHA256 Webhook Dispatcher] ---> Відправка POST /api/revalidate |
| (Payload: { tag: "post:42", ts: 1774600000 }) |
| (Header: X-Agentic-Signature: e3b0c44...) |
+-----------------------------------------------------------------------------------------+
|
| Безпечний HTTP запит
v
+-----------------------------------------------------------------------------------------+
| NEXT.JS 16 EDGE ROUTE HANDLER |
+-----------------------------------------------------------------------------------------+
| [/api/revalidate/route.ts] |
| | |
| +---> [Timing-Safe HMAC Verification: crypto.timingSafeEqual()] |
| | | (Захист від атак за часом і неавторизованого скидання) |
| | v |
| +---> [revalidateTag("post:42")] |
| | |
| v |
| [Edge Data Cache / Full Route Cache: Tag Purged!] |
+-----------------------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------------------+
| ПУБЛІЧНІ КОРИСТУВАЧІ |
+-----------------------------------------------------------------------------------------+
| Користувач А (Запит через 10 мс): Отримує Stale Cache (30 мс) |
| |---> Next.js у фоні виконує 1 запит до WP API та оновлює кеш |
| Користувач Б (Запит через 500 мс): Отримує СВІЖИЙ оновлений HTML! |
+-----------------------------------------------------------------------------------------+Технологія Next.js, що дозволяє оновлювати статичні сторінки вебсайту у фоновому режимі без необхідності повторної повної компіляції всього проєкту.
Програмне скидання кешованих даних за асоційованим тегом (cache tag), ініційоване зовнішнім вебхуком при зміні контенту в CMS.
Декларативна директива Next.js 16 на рівні функцій або компонентів, що замінює застарілі параметри fetch і дозволяє гранулярно налаштовувати життєвий цикл кешу (stale, revalidate, expire).
Патерн обслуговування запитів, при якому під час фонового оновлення кешу користувачам миттєво віддається злегка застаріла версія, виключаючи лавиноподібні запити до бази даних.
Конфігурація маршруту, що дозволяє компілювати на етапі збірки лише ключовий зріз сторінок, тоді як мільйони інших сторінок генеруються та кешуються за вимогою.
Редактор оновлює статтю в адмінці. Спрацьовує хук save_post або transition_post_status. Плагін збирає метадані: ID публікації, слаг, рубрики.
PHP-модуль формує цифровий підпис корисного навантаження на основі алгоритму HMAC-SHA256 із використанням секретного ключа REVALIDATION_SECRET.
Асинхронний POST-запит передається на ендпоінт https://frontend.company.com/api/revalidate із заголовком X-Agentic-Signature.
Route Handler у Next.js перевіряє підпис через crypto.timingSafeEqual. При збігу викликається revalidateTag('post:42') та revalidateTag('posts').
Edge-шар миттєво позначає кеш маршруту як застарілий. Наступний запит запускає неблокуюче фонове оновлення за патерном Stale-While-Revalidate.
generateStaticParams), тоді як картки 500 000 об'єктів генеруються за вимогою при першому переході з пошуку.posts інвалідує стрічку новин на головній, а тег post:${id} оновлює лише конкретну статтю, зберігаючи 99.9% кешу недоторканим.product:stock:${sku} при отриманні вебхука від складської системи.wp:menus при зміні посилань в адмінці, що оновлює шапку та підвал на всьому сайті без перезбірки сторінок.Якщо в інтернет-магазині або блозі міститься 100 000 публікацій, спроба згенерувати всі сторінки в generateStaticParams() призведе до вичерпання оперативної пам'яті та падіння збирача Next.js за таймаутом.
Стандарт архітектури:
// app/blog/[slug]/page.tsx
export const dynamicParams = true; // Дозволяємо генерацію невідомих слагів на льоту
export async function generateStaticParams() {
// Вибираємо лише 100 найпопулярніших / свіжих публікацій
const topPosts = await wpClient.getTopPosts({ limit: 100 });
return topPosts.map((post) => ({ slug: post.slug }));
}За такої конфігурації:
next build збирає лише 100 сторінок за кілька секунд.Нижче наведено промислову зв'язку: PHP-диспатчер вебхуків для WordPress із підписом HMAC-SHA256 та безпечний Route Handler для Next.js 16 із захистом від таймінг-атак.
// ============================================================================
// ЧАСТИНА 1: Next.js 16 Route Handler (app/api/revalidate/route.ts)
// ============================================================================
import { NextRequest, NextResponse } from 'next/server';
import { revalidateTag } from 'next/cache';
import crypto from 'node:crypto';
interface RevalidatePayload {
tag?: string;
slug?: string;
id?: number;
timestamp: number;
}
/**
* Безпечна перевірка підпису HMAC-SHA256 із захистом від Timing Attacks.
*/
function verifyHmacSignature(rawBody: string, signatureHeader: string | null, secret: string): boolean {
if (!signatureHeader || !secret) {
return false;
}
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const trustedBuffer = Buffer.from(expectedSignature, 'utf8');
const userBuffer = Buffer.from(signatureHeader, 'utf8');
if (trustedBuffer.length !== userBuffer.length) {
return false;
}
return crypto.timingSafeEqual(trustedBuffer, userBuffer);
}
export async function POST(request: NextRequest): Promise<NextResponse> {
const secret = process.env.REVALIDATION_SECRET;
if (!secret) {
console.error('[Revalidate] REVALIDATION_SECRET is not configured on edge.');
return NextResponse.json({ message: 'Server configuration error' }, { status: 500 });
}
try {
const rawBody = await request.text();
const signature = request.headers.get('X-Agentic-Signature');
// 1. Криптографічна валідація джерела
if (!verifyHmacSignature(rawBody, signature, secret)) {
return NextResponse.json({ message: 'Invalid cryptographic signature' }, { status: 401 });
}
const payload = JSON.parse(rawBody) as RevalidatePayload;
// 2. Захист від Replay-атак (вікно 5 хвилин)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - payload.timestamp) > 300) {
return NextResponse.json({ message: 'Timestamp outside acceptable window' }, { status: 400 });
}
const tagsToPurge: string[] = [];
if (payload.tag) {
tagsToPurge.push(payload.tag);
}
if (payload.id) {
tagsToPurge.push(`post:${payload.id}`);
}
if (payload.slug) {
tagsToPurge.push(`post:${payload.slug}`);
}
// Завжди інвалідуємо загальний список публікацій
tagsToPurge.push('posts');
// 3. Атомарне скидання тегів у Data Cache Next.js
for (const tag of tagsToPurge) {
revalidateTag(tag);
}
return NextResponse.json({
revalidated: true,
purgedTags: tagsToPurge,
timestamp: Date.now(),
});
} catch (error) {
console.error('[Revalidate] Error processing webhook:', error);
return NextResponse.json({ message: 'Error parsing request body' }, { status: 400 });
}
}
// ============================================================================
// ЧАСТИНА 2: WordPress Dispatcher (wp-content/mu-plugins/agentic-revalidation.php)
// Код на PHP 8.4+ для відправки підписаних вебхуків із ядра WordPress:
// ============================================================================
/*
<?php
declare(strict_types=1);
namespace AgenticLab\Integration;
final class AgenticRevalidationDispatcher {
private const string REVALIDATE_URL = 'https://frontend.agenticlab.com/api/revalidate';
public static function init(): void {
add_action('transition_post_status', [self::class, 'handle_status_transition'], 10, 3);
add_action('wp_trash_post', [self::class, 'handle_post_deletion'], 10, 1);
}
public static function handle_status_transition(string $new_status, string $old_status, \WP_Post $post): void {
// Спрацьовує тільки при публікації, оновленні або знятті з публікації
if ($new_status !== 'publish' && $old_status !== 'publish') {
return;
}
if (wp_is_post_revision($post->ID) || wp_is_post_autosave($post->ID)) {
return;
}
self::dispatch_revalidation([
'id' => $post->ID,
'slug' => $post->post_name,
'tag' => 'post:' . $post->ID,
'timestamp' => time(),
]);
}
public static function handle_post_deletion(int $post_id): void {
self::dispatch_revalidation([
'id' => $post_id,
'tag' => 'post:' . $post_id,
'timestamp' => time(),
]);
}
private static function dispatch_revalidation(array $payload): void {
$secret = defined('NEXTJS_REVALIDATION_SECRET') ? NEXTJS_REVALIDATION_SECRET : '';
if (empty($secret)) {
return;
}
$json_payload = wp_json_encode($payload);
$signature = hash_hmac('sha256', $json_payload, $secret);
wp_remote_post(self::REVALIDATE_URL, [
'method' => 'POST',
'timeout' => 5,
'blocking' => false, // Не блокуємо адмінку WP, відправка асинхронна!
'headers' => [
'Content-Type' => 'application/json',
'X-Agentic-Signature' => $signature,
],
'body' => $json_payload,
]);
}
}
add_action('init', [AgenticRevalidationDispatcher::class, 'init']);
*/
Розмітка всіх викликів fetch параметрами next: { tags: ['posts', `post:${slug}`] }. Налаштування експорту dynamicParams = true.
Створює гранулярну структуру залежностей кешу.
Реалізація ендпоінта /api/revalidate з валідацією криптографічного підпису HMAC-SHA256 через crypto.timingSafeEqual.
Унеможливлює несанкціоноване скидання кешу CDN.
Розміщення модуля в mu-plugins із прив'язкою до хука transition_post_status. Передача підписаних асинхронних запитів.
Забезпечує миттєвий тригер інвалідації без затримок у роботі адмінки.
Редагування тестової статті в адмінці, перевірка логів виконання вебхука та підтвердження оновлення HTML на фронтенді за 100 мс.
Гарантує бездоганну роботу в продакшені.
Побудова високопродуктивного шару доставки контенту на базі Next.js 16 та WordPress базується на трьох правилах:
revalidateTag).crypto.timingSafeEqual.generateStaticParams() у поєднанні з лінивою фоновою генерацією dynamicParams = true.