Интеграция CMS Headless WordPress (WP REST API + фронтенд)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Интеграция CMS Headless WordPress (WP REST API + фронтенд)
Сложная
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Интеграция CMS Headless WordPress (WP REST API + фронтенд)

Headless WordPress — архитектура, где WordPress отвечает только за управление контентом и предоставляет его через API. Фронтенд полностью отдельный: Next.js, Nuxt, React SPA или любой другой стек. Редакторы работают в привычном интерфейсе WordPress, разработчики получают свободу выбора технологий на фронте.

Когда это оправдано

Headless добавляет сложность. Оправдан, когда:

  • уже есть React/Next.js-фронтенд и нужна CMS под него;
  • контент нужно отдавать на несколько платформ (сайт + мобильное приложение + newsletter);
  • требуется ISR/SSG на Next.js с обновлением без полного деплоя;
  • команда фронтендеров не хочет работать с PHP-шаблонами.

Не нужен, если сайт строится с нуля и нет жёсткого требования к стеку фронтенда — обычный WordPress проще.

WP REST API: базовые эндпоинты

WordPress REST API включён из коробки с версии 4.7. Базовый URL: https://site.com/wp-json/wp/v2/.

# Список постов
GET /wp-json/wp/v2/posts?per_page=10&page=1&_fields=id,title,slug,date,excerpt

# Один пост по slug
GET /wp-json/wp/v2/posts?slug=my-post-slug

# Страницы
GET /wp-json/wp/v2/pages?slug=about

# Таксономии
GET /wp-json/wp/v2/categories
GET /wp-json/wp/v2/tags?post=123

# Custom Post Type (должен быть зарегистрирован с show_in_rest=true)
GET /wp-json/wp/v2/portfolio?per_page=6&acf_format=standard

Параметр _fields критически важен — по умолчанию ответ содержит десятки полей, большинство из которых не нужны:

GET /wp-json/wp/v2/posts?_fields=id,title,slug,date,featured_media,excerpt,_links

Настройка WordPress для headless

Отключить тему (фронтенд не нужен):

// functions.php
add_action('template_redirect', function () {
    if (!is_admin() && !is_user_logged_in()) {
        // Перенаправить все фронтенд-запросы на фронтенд-домен
        if (!str_starts_with($_SERVER['REQUEST_URI'], '/wp-json')) {
            wp_redirect('https://frontend.site.com' . $_SERVER['REQUEST_URI'], 301);
            exit;
        }
    }
});

CORS для запросов с фронтенда:

add_action('rest_api_init', function () {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');

    add_filter('rest_pre_serve_request', function ($value) {
        $allowed_origins = [
            'https://frontend.site.com',
            'http://localhost:3000',
        ];

        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
        if (in_array($origin, $allowed_origins, true)) {
            header("Access-Control-Allow-Origin: {$origin}");
            header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
            header('Access-Control-Allow-Headers: Authorization, Content-Type');
            header('Access-Control-Allow-Credentials: true');
        }
        return $value;
    });
}, 15);

Расширение REST API своими данными

ACF-поля в REST API через плагин acf-to-rest-api или вручную:

// Добавить ACF-поля в ответ REST API для portfolio
add_action('rest_api_init', function () {
    register_rest_field('portfolio', 'acf', [
        'get_callback' => function ($post) {
            return get_fields($post['id']);
        },
        'schema' => ['type' => 'object'],
    ]);

    // Добавить URL изображения прямо в ответ
    register_rest_field('post', 'featured_image_url', [
        'get_callback' => function ($post) {
            $id = $post['featured_media'];
            if (!$id) return null;

            $img = wp_get_attachment_image_src($id, 'large');
            return $img ? $img[0] : null;
        },
        'schema' => ['type' => ['string', 'null']],
    ]);
});

Кастомный эндпоинт для нестандартных запросов:

add_action('rest_api_init', function () {
    register_rest_route('app/v1', '/home', [
        'methods'  => 'GET',
        'callback' => function (WP_REST_Request $request) {
            return rest_ensure_response([
                'hero'     => get_fields(get_option('home_hero_page_id')),
                'featured' => array_map(
                    fn($post) => [
                        'id'    => $post->ID,
                        'title' => get_the_title($post),
                        'slug'  => $post->post_name,
                        'image' => get_the_post_thumbnail_url($post, 'medium'),
                    ],
                    get_posts(['post_type' => 'portfolio', 'posts_per_page' => 3])
                ),
            ]);
        },
        'permission_callback' => '__return_true',
    ]);
});

Интеграция с Next.js

// lib/wordpress.ts
const WP_API = process.env.WP_API_URL; // https://cms.site.com/wp-json/wp/v2

export interface WpPost {
    id: number;
    slug: string;
    title: { rendered: string };
    excerpt: { rendered: string };
    date: string;
    featured_image_url: string | null;
    acf?: Record<string, unknown>;
}

export async function getPosts(params: {
    perPage?: number;
    page?: number;
    category?: number;
} = {}): Promise<{ posts: WpPost[]; total: number; totalPages: number }> {
    const url = new URL(`${WP_API}/posts`);
    url.searchParams.set('per_page', String(params.perPage ?? 10));
    url.searchParams.set('page',     String(params.page     ?? 1));
    url.searchParams.set('_fields',  'id,slug,title,excerpt,date,featured_image_url,acf');
    if (params.category) {
        url.searchParams.set('categories', String(params.category));
    }

    const res = await fetch(url.toString(), {
        next: { revalidate: 60 }, // ISR: обновлять не чаще раза в минуту
    });

    if (!res.ok) throw new Error(`WP API error: ${res.status}`);

    return {
        posts:      await res.json(),
        total:      Number(res.headers.get('X-WP-Total')),
        totalPages: Number(res.headers.get('X-WP-TotalPages')),
    };
}

export async function getPostBySlug(slug: string): Promise<WpPost | null> {
    const res = await fetch(
        `${WP_API}/posts?slug=${slug}&_fields=id,slug,title,content,date,featured_image_url,acf`,
        { next: { revalidate: 300 } }
    );
    const data = await res.json();
    return data[0] ?? null;
}

Preview Mode

Для предпросмотра черновиков из WordPress в Next.js:

// app/api/preview/route.ts
import { draftMode } from 'next/headers';
import { redirect }  from 'next/navigation';

export async function GET(req: Request) {
    const { searchParams } = new URL(req.url);
    const secret   = searchParams.get('secret');
    const postSlug = searchParams.get('slug');

    if (secret !== process.env.WP_PREVIEW_SECRET || !postSlug) {
        return new Response('Invalid token', { status: 401 });
    }

    draftMode().enable();
    redirect(`/blog/${postSlug}`);
}

В WordPress добавляем кнопку «Preview» которая редиректит на: https://frontend.site.com/api/preview?secret=SECRET&slug=POST_SLUG

Кэширование на стороне WordPress

REST API не кэшируется по умолчанию. Добавляем кэш через транзиент:

add_filter('rest_post_query', function ($args, $request) {
    // Кэшируем только GET-запросы от неавторизованных
    if ($request->get_method() === 'GET' && !is_user_logged_in()) {
        $cache_key = 'rest_' . md5(serialize($args));
        $cached    = get_transient($cache_key);
        if ($cached !== false) {
            return $cached; // не совсем правильно для query args, но как иллюстрация
        }
    }
    return $args;
}, 10, 2);

Более надёжное решение — Redis Object Cache или Nginx-кэш перед WordPress для REST-эндпоинтов только для анонимных запросов.

On-demand Revalidation (Next.js ISR)

WordPress уведомляет Next.js о публикации поста:

// В WordPress: webhook при сохранении поста
add_action('save_post', function (int $postId, WP_Post $post) {
    if ($post->post_status !== 'publish') return;

    wp_remote_post('https://frontend.site.com/api/revalidate', [
        'method'  => 'POST',
        'headers' => ['Content-Type' => 'application/json'],
        'body'    => json_encode([
            'secret' => getenv('REVALIDATE_SECRET'),
            'slug'   => $post->post_name,
            'type'   => $post->post_type,
        ]),
        'timeout' => 5,
    ]);
}, 10, 2);
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(req: Request) {
    const { secret, slug, type } = await req.json();
    if (secret !== process.env.REVALIDATE_SECRET) {
        return Response.json({ error: 'Forbidden' }, { status: 403 });
    }

    const path = type === 'post' ? `/blog/${slug}` : `/${slug}`;
    revalidatePath(path);
    revalidatePath('/blog'); // обновляем список статей

    return Response.json({ revalidated: true });
}

Сроки

Настройка WordPress под headless (CORS, ACF в API, кастомные эндпоинты) — 6–8 часов. Интеграция с Next.js (клиент API, ISR, preview mode) — 1–1,5 рабочих дня. Вебхуки on-demand revalidation — 3–4 часа.