Как подключить React к WordPress через API

Как подключить React к WordPress через API

Полгода назад передо мной встала задача — превратить обычный WordPress-сайт клиента в быстрое SPA-приложение, сохранив привычную для контент-менеджеров админку. Делюсь пошаговым решением, которое я обкатал на трёх проектах.

Почему именно этот подход

WordPress REST API появился в ядре с версии 4.7, и это освободило разработчиков от шаблонов PHP. Теперь WordPress можно использовать как headless CMS — только для хранения и выдачи данных, а фронтенд полностью отдать React.

Шаг 1: Подготовка WordPress

Первым делом я убеждаюсь, что REST API включен (по умолчанию он активен). Проверить это просто — открываем в браузере:

https://ваш-сайт.ru/wp-json/wp/v2/posts

Если видите JSON с постами — всё работает. Если нет, проверьте плагины безопасности, которые могут блокируют API.

Важный момент, который я всегда настраиваю — CORS. Без этого React-приложение с другого домена не получит доступ к данным. Добавляю в functions.php темы:

function add_cors_http_header(){
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    header("Access-Control-Allow-Headers: Content-Type, Authorization");
}
add_action('init', 'add_cors_http_header');

Для продакшена рекомендую заменить * на конкретный домен вашего React-приложения — это безопаснее.

Шаг 2: Создание React-приложения

Использую Vite для создания React приложения:

npm create vite@latest my-wp-app -- --template react
cd my-wp-app
npm install
npm install axios

Axios беру вместо fetch — удобнее обрабатывать запросы и ошибки.

Шаг 3: Получение данных из WordPress

Создаю отдельный файл для API-запросов — так проект остаётся чистым и масштабируемым app/api/wordpress.js:

import axios from 'axios';

const API_URL = 'https://site.ru/wp-json/wp/v2';

export const getPosts = async () => {
  try {
    const response = await axios.get(`${API_URL}/posts?_embed`);
    return response.data;
  } catch (error) {
    console.error('Ошибка загрузки:', error);
    throw error;
  }
};

export const getPostBySlug = async (slug) => {
  const response = await axios.get(`${API_URL}/posts?slug=${slug}&_embed`);
  return response.data[0];
};

Параметр _embed — моя любимая фишка. Он подгружает связанные данные (изображения, авторов, категории) одним запросом, избавляя от лишних обращений к серверу.

Шаг 4: Компонент для вывода постов

Создаем компонент app/components/PostList.jsx:

import { useState, useEffect } from 'react';
import { getPosts } from '../api/wordpress';

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const data = await getPosts();
        setPosts(data);
      } catch (err) {
        setError('Не удалось загрузить статьи');
      } finally {
        setLoading(false);
      }
    };
    fetchPosts();
  }, []);

  if (loading) return <div className="loader">Загрузка...</div>;
  if (error) return <div className="error">{error}</div>;

  return (
    <div className="post-list">
      {posts.map((post) => {
        const imageUrl = post._embedded?.['wp:featuredmedia']?.[0]?.source_url;
        
        return (
          <article key={post.id} className="post-card">
            {imageUrl && <img src={imageUrl} alt={post.title.rendered} />}
            <h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
            <div 
              className="excerpt"
              dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} 
            />
          </article>
        );
      })}
    </div>
  );
}

export default PostList;

Обратите внимание на dangerouslySetInnerHTML — WordPress возвращает контент как HTML-строку, и это единственный способ корректно отрендерить теги форматирования, которые редакторы используют в Gutenberg.

Шаг 5: Работа с пользовательскими данными

Когда стандартных полей posts недостаточно, я подключаю плагин Advanced Custom Fields (ACF) с расширением ACF to REST API. Это добавляет кастомные поля в JSON-ответ:

export const getProductData = async (id) => {
  const response = await axios.get(`${API_URL}/product/${id}?_fields=acf,title`);
  return response.data;
};

Шаг 6: Аутентификация для форм и приватных данных

Если нужна отправка форм или доступ к защищённым эндпоинтам, использую JWT Authentication плагин:

export const authenticate = async (username, password) => {
  const response = await axios.post(
    'https://site.ru/wp-json/jwt-auth/v1/token',
    { username, password }
  );
  localStorage.setItem('token', response.data.token);
  return response.data;
};

// Использование токена в запросах
const token = localStorage.getItem('token');
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;

Подводные камни, на которые я наткнулся

Пагинация

WordPress по умолчанию отдаёт 10 постов. Для навигации использую заголовки ответа X-WP-TotalPages:

const response = await axios.get(`${API_URL}/posts?page=${page}&per_page=12`);
const totalPages = response.headers['x-wp-totalpages'];

Кэширование

На высоконагруженных проектах подключаю React Query — он избавляет от повторных запросов и делает UX плавнее:

npm install @tanstack/react-query

SEO

Это главный минус SPA — поисковики хуже индексируют динамический контент. Для решения использую React Helmet для метатегов или перехожу на Next.js с SSR, если SEO критично.

Итог

Связка React + WordPress REST API даёт лучшее из двух миров: привычный админ-интерфейс для контент-менеджеров и современный быстрый фронтенд для пользователей. За полгода использования этого стека на трёх проектах я убедился — это рабочее решение для блогов, интернет-магазинов и корпоративных сайтов, где важна скорость загрузки и гибкость интерфейса.