Headless WordPress CMS Integration (WP REST API + Frontend)

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Headless WordPress Integration (WP REST API + Frontend)

Headless WordPress is architecture where WordPress manages content only and provides it via API. Frontend completely separate: Next.js, Nuxt, React SPA or any other stack. Editors work in familiar WordPress interface, developers have technology freedom on frontend.

When It's Justified

Headless adds complexity. Justified when:

  • existing React/Next.js frontend needs CMS;
  • content delivery to multiple platforms (site + mobile app + newsletter);
  • ISR/SSG on Next.js with update without full deploy required;
  • frontend team doesn't want to work with PHP templates.

Not needed if building site from scratch without strict frontend stack requirement — regular WordPress simpler.

WP REST API: Basic Endpoints

WordPress REST API included out-of-box since 4.7. Base URL: https://site.com/wp-json/wp/v2/.

# List posts
GET /wp-json/wp/v2/posts?per_page=10&page=1&_fields=id,title,slug,date,excerpt

# Single post by slug
GET /wp-json/wp/v2/posts?slug=my-post-slug

# Pages
GET /wp-json/wp/v2/pages?slug=about

# Taxonomies
GET /wp-json/wp/v2/categories
GET /wp-json/wp/v2/tags?post=123

# Custom Post Type (must register with show_in_rest=true)
GET /wp-json/wp/v2/portfolio?per_page=6&acf_format=standard

Parameter _fields critically important — by default response contains dozens of fields, most unnecessary:

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

WordPress Headless Configuration

Disable theme (frontend not needed):

// functions.php
add_action('template_redirect', function () {
    if (!is_admin() && !is_user_logged_in()) {
        // Redirect all frontend requests to frontend domain
        if (!str_starts_with($_SERVER['REQUEST_URI'], '/wp-json')) {
            wp_redirect('https://frontend.site.com' . $_SERVER['REQUEST_URI'], 301);
            exit;
        }
    }
});

CORS for frontend requests:

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);

Extending REST API with Custom Data

ACF fields in REST API via acf-to-rest-api plugin or manually:

// Add ACF fields to REST API response for portfolio
add_action('rest_api_init', function () {
    register_rest_field('portfolio', 'acf', [
        'get_callback' => function ($post) {
            return get_fields($post['id']);
        },
        'schema' => ['type' => 'object'],
    ]);

    // Add featured image URL directly to response
    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']],
    ]);
});

Custom endpoint for non-standard requests:

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 Integration

// 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: update no more than once per minute
    });

    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;
}

Timeline

WordPress headless setup (CORS, ACF in API, custom endpoints) — 6–8 hours. Next.js integration (API client, ISR, preview mode) — 1–1.5 working days. Webhooks on-demand revalidation — 3–4 hours.