Blog

ROI SÁNCHEZ
20 May 2022

Convertir WordPress en un Headless CMS

Tiempo de lectura 10 minutos
  • cache
  • headless cms
  • web
  • wordPress

Sumario En este artículo intentaré explicar como convertir tu instalación Wordpress en un headless CMS, pero para eso primero tenemos que entender qué es un headless CMS.

Headless CMS, ¿qué es eso?

Bueno, primero vamos a empezar por lo básico. ¿Qué es un CMS? Un CMS o Content Management System es un sistema (en nuestro caso web) que permite gestionar el contenido, es decir, es un aplicativo ya desarrollado que te permite crear páginas, subir imágenes, editar textos, crear artículos de blog como este, etc; de forma que “solo” te tengas que preocupar de: configurarlo correctamente según lo que necesites; crear todo el contenido; configurar, desarrollar o adaptar el front end o sistema de visualización de este contenido. 

Los CMS tradicionales, como WordPress, tienen un back end donde editas el contenido y un front end que es lo que se expone a los usuarios finales para que consulten la web. 

Un headless CMS funciona de una forma un poco diferente, ya que no expone un front end para que muestres tu web a los usuarios, sino que solo expone un API que puede ser consumido por diferentes front end. Así puedes tener varios front end presentando la web de formas diferentes, por ejemplo por que van dedicadas a clientes diferentes, porque quieres tener distintas aplicaciones según el dispositivo, o por el motivo que quieras. 

Además, tener desacoplado el front end del back end te permite, de una forma más sencilla, integrarte con varios back ends, por ejemplo en función de la sección o funcionalidad del sitio web. 

Recordamos las ventajas e inconvenientes de este tipo de aproximación a un CMS que ya desglosamos en un artículo anterior: 

Ventajas: 

  • No tenemos acoplamiento entre aplicaciones de negocio y el CMS. Lo que nos permite escoger la tecnología y framework que queramos para el front end. 
  • Normalmente son más ligeros y fáciles de desplegar y usar (En nuestro caso esta ventaja no aplica). 
  • Posibilidad de integrar nuevos canales o funcionalidades sin limitaciones por parte del CMS. 
  • Encaja perfectamente con una arquitectura de microservicios. 
  • Mejora la escalabilidad y seguridad al permitir entrar diferentes piezas para cada propósito. 

Desventajas 

  • Adiós a las previsualizaciones del contenido antes de publicar. (Aunque en nuestro caso lo hemos paliado parcialmente y tenemos en mente mejoras en este aspecto) 
  • Las típicas funcionalidades de análisis y personalización que te da por defecto el CMS las tienes que implementar por tu lado. 

Convertir WordPress en un headless CMS 

Pues bien, como dijimos anteriormente, WordPress no es un headless CMS de base, pero podemos hacer que lo parezca.  

Pero si no es un headless CMS, ¿Por qué usar WordPress en lugar de otro CMS que sí lo sea? Es una buena pregunta 😊El primer punto es que WordPress es un sistema tremendamente conocido hoy en día, se podría decir que es prácticamente un estándar. Por lo tanto, es muy probable que los usuarios que finalmente tengan que mantener el sitio web ya conozcan WordPress y estén familiarizados con su interfaz. Esto nos ahorra muchísimo tiempo de formación y soporte. Además, tiene un montón de plugins que podremos usar para mejorar el SEO de nuestro sitio, configurar el envío de emails o integrarnos con otras aplicaciones (3rd parties). La interfaz de edición de contenidos está bastante trabajada y con el sistema de Gutemberg es realmente sencillo gestionar el contenido. 

Bien, una vez decidido que usamos WordPress vamos a ponernos manos a la obra para convertirlo en nuestro headless CMS de cabecera.  

Tema propio a medida 

El primer punto es que nos vamos a crear un tema personalizado. Como no necesitamos que WordPress muestre contenido alguno a través de su front end no necesitamos ninguno de los temas disponibles. 

Para crear tu propio tema accede a la carpeta wp_content/themes y crea una nueva carpeta con el nombre del tema que quieres crear. Por ejemplo, en nuestro lo hemos llamado helloworld. Dentro de esta carpeta crea un fichero index.php y déjalo en blanco. Además, crea un fichero style.css y también déjalo en blanco. Con esto ya tenemos el tema con lo mínimo para que WordPress no dé error pero si algún usuario accede a la url del front end no va a visualizar nada, por lo que se puede decir que no estamos devolviendo nada al usuario final desde WordPress. 

Devolver el contenido de las páginas y posts en un API rest 

Aunque no vamos a tener un front end que devuelva las páginas al cliente, obviamente algo tenemos que tener para que el aplicativo que haga de front end pueda mostrar las páginas y el contenido mantenido en WordPress. Lo que vamos a hacer para esto es usar el API de WordPress. Aquí tenemos básicamente dos opciones, o bien usar el API nativo de WordPress o crearnos nosotros un api a nuestra medida.  

En caso de optar por la primera opción, API nativo de WordPress , aquí puedes ver la documentación: https://developer.wordpress.org/rest-api/. Como ejemplo puedes probar a hacer la llamada get https://[tu dominio]/wp-json/wp/v2/posts y verás todos los posts creados en el back-end. 

En caso de optar por crear tu propio api puedes consultar en este enlace la documentación oficial https://developer.wordpress.org/rest-api/extending-the-rest-api/, aunque explicaré a continuación como lo hemos montado nosotros. 

Para tener nuestro código bien ordenado nos creamos una carpeta “api” dentro de nuestro tema en el que incluimos todos los ficheros php que va a gestionar nuestra api rest. En esta carpeta tenemos varios ficheros divididos funcionalmente: fichero para los métodos de API de test de preguntas, fichero de métodos para el api de menús y navegación, fichero para el api de carga de contenido de posts, etc. 

Estos ficheros los agrupamos todos bajo el namespace «helloworld\api» de forma que lógicamente también estén organizados, creamos una clase en cada uno de ellos y registramos el endpoint en el constructor del mismo. Un ejemplo simplificado y sin toda la implementación del mismo podría ser el siguiente: 

<?php 


namespace helloworld\api { 

    class api_post{ 
        function __construct(){ 
            add_action( api_config::ACTION_REST_API, function () { 
                register_rest_route(  api_config::SWEB_NAMESPACE, '/home/blocks, array( 
                    'methods' => 'GET', 
                    'callback' => array( $this 'get_home_blocks'), 
                    'permission_callback' => '__return_true', 
                    ) 
                );     
            }); 
        } 

        public function get_home_blocks( $data ){             
            /* Implementación del método */                
        } 
    } 
    new api_post; 
} 
?>

Como se puede ver tenemos una clase api_post, en cuyo constructor se registra el endpoint, tenemos un método público con la implementación del endpoint y el propio fichero instancia la clase.  

Para forzar la instanciación, lo único que hacemos es incluirlo en el functions.php de nuestro tema: 

require get_template_directory() . '/api/ api_post.php'; 

Para recuperar el contenido de nuestras páginas y entradas tenemos varias opciones. Nosotros hemos optado por devolver los bloques en un array, ya que solo vamos a devolver los campos (el contenido) no la maquetación. La maquetación será responsabilidad de nuestro front end, Angular en nuestro caso. Para cada bloque podremos devolver directamente el contenido o los campos en una estructura formateada, en función en cada caso de lo que necesitemos. Pensamos que esta es la decisión más lógica, ya que si WordPress se va a encargar de gestionar el contenido, pero el Angular va a ser el front, lo lógico es que en la medida de lo posible sea Angular el que se encargue de la maquetación. Solo nos saltamos esta regla en las partes en las que damos libertad al editor para formatear contenido (intentamos que sea lo mínimo posible).

Devolver los menús y el sitemap como API 

De la misma forma que devolvemos el contenido de las entradas y páginas tenemos que devolver los menús. La parte de creación de endpoints del api es la misma que para las páginas y entradas, pero tenemos que pensar cómo vamos a devolver la información de los menús, para que puedan ser manejadas desde la aplicación cliente. 

La primera opción que nos vino a la cabeza fue devolver los menús con un Walker de WordPress, así podíamos devolverlos ya totalmente formateados, pero al momento nos dimos cuenta de que no tenía sentido, ya que era ir en contra de la filosofía que estamos buscando. La maquetación debe ser responsabilidad de la aplicación Angular, no del back end WordPress, y por tanto lo descartamos. De cabeza a la siguiente opción: devolver los menús en json. 

Para devolver los menús en un formato json el primer paso fue crearnos nuestro fichero de api de menú, nos creamos un endpoint para cada menú y ese endpoint se encarga de recuperar los elementos de menú de forma jerárquica y componer el json. 

Para poder reutilizar código, evitar duplicidades e intentar seguir los principios de código limpio nos creamos un método que recupera el menú en función del nombre así, por ejemplo para recuperar el menú principal tenemos: 

Registro del endpoint 

register_rest_route(  api_config::SWEB_NAMESPACE, '/menu/main', array( 
     'methods' => 'GET', 
     'callback' => array( $this, 'get_main_menu'), 
     'permission_callback' => '__return_true', 
   ) 
); 

Método público al que llama el endpoint 

public function get_main_menu( $data ){ 
  return $this->get_menu_by_name("Principal"); 
} 

 Este método únicamente llama al método de recuperación del json del menú indicándole el nombre del menú que tiene que buscar. 

El método get_menu_by_name, como su propio nombre indica se encarga de recuperar el menú en base al nombre, por lo que su lógica es la siguiente: 

  1. Recupera los elementos de menú de primer nivel 
  1. Para cada elemento: 
    1. Recupera la clase de item_menu que vamos a devolver (no devolvemos al front todos los datos, solo lo que necesitará) 
    2. Recupera todos sus elementos de menú hijos 
    3. Para cada hijo hace el mismo proceso 
  2. Añade el elemento del menú al array que devolverá como json 
private function get_menu_by_name($menuName){ 
   $menu_items = array(); 
   $menu_items_orig = array_filter(wp_get_nav_menu_items($menuName), function($item){ 
      return $item->menu_item_parent == "0"; 
   }); 

  foreach($menu_items_orig as $menu_item_orig){ 
     $menu_item = $this->get_menu_item_main($menu_item_orig); 
     $menu_item_children_orig = array_filter(wp_get_nav_menu_items($menuName), function($item) use ($menu_item_orig){ 
        return $item->menu_item_parent == $menu_item_orig->ID; 
     }); 

     if($menu_item_children_orig){ 
         $menu_item->children = array(); 
         foreach($menu_item_children_orig as $child_item_orig){ 
            array_push($menu_item->children, $this->get_menu_item_main($child_item_orig)); 
         } 
      } 
      array_push($menu_items, $menu_item); 

   } 
   return $menu_items; 
} 

Adicionalmente a la devolución de los menús también tendremos que generar un endpoint con el sitemap de la aplicación, es decir un endpoint que devuelva todas las rutas de la aplicación (páginas y entradas), ya que es necesario para que el front end en Angular pueda generar todo el routing necesario de la aplicación y la navegación funcione. 

Desarrollo de plantillas de página, cpts, etc 

El desarrollo de plantillas de página, bloques de contenido, custom psot types, etc es el desarrollo normal de un proyecto WordPress, simplemente es necesario tener en mente cómo se va a devolver la información para organizarlo de una forma que no nos complique la vida demasiado en fases posteriores. 

Configuración del editor 

Una de las pegas que tiene tener un sistema de contenidos Headless CMS es que no tienes la posibilidad de preview, por esto cobra vital importancia la configuración del editor Gutemberg. Nuestra aproximación a este problema ha sido la siguiente: 

  1. A la hora de crear un componente partimos del diseño en imagen 
  2. Lo primero que hacemos crear el componente en Angular con datos estáticos y maquetarlo. 
  3. Una vez maquetado en Angular creamos el bloque en WordPress y aprovechamos para crear el html de visualización del bloque y sus estilos (mediante ficheros separados en Sass)
  4. En WordPress creamos el código necesario para la configuración, en nuestro caso nos apoyamos en ACF Pro, y lo probamos dentro del editor Gutemberg 
  5. Hacemos los ajustes necesarios en nuestro api 
  6. Cambiamos el componente de Angular para que reciba los datos del api dinámicamente y modificamos la vista. 

Para tener los estilos de cada bloque bien organizamos usamos sass que compilamos en un el fichero editor-styles.css. A WordPress le indicamos que debe ser este fichero el que use para los estilos del editor con este código en el functions de nuestro tema. 

add_editor_style( array( 'css/editor-style.css', hw_fonts_url() ) ); 
// Load regular editor styles into the new block-based editor. 
add_theme_support( 'editor-styles' ); 

Caché 

Uno de los puntos que nos preocupaban mucho con este sistema era el rendimiento de nuestra nueva web, ya que es una web pública en la que queríamos hacer hincapié en su velocidad. Al final, como tampoco es una web que debiera tener un nivel de cambios muy exhaustivo, decidimos cachear todos los endpoints con el plugin WP REST Cache, vaciando la caché cada vez que actualizamos contenido.  

Con esta estrategia más usar nginx como servidor de aplicaciones conseguimos tener una respuesta de cada endpoint que ronda los 60 milisegundos. 

Arquitectura de sistemas para diferenciar WordPress de web pública

La problemática que teníamos en este punto era que necesitábamos poner accesible nuestra web para el público, pero de alguno forma también necesitábamos tener una entrada al editor. Pero no pueden estar directamente en la misma url, ya que no usamos el front end de WordPress.  

La solución al final es relativamente sencilla. Ambos sistemas los publicamos en la misma máquina, pero en diferentes dominios, y además publicamos en otro puerto internamente el back end para que, mediante un proxy inverso el front end pueda realizar llamadas al api de WordPress. 

Arquitectura del sistema

Autor

ROI SÁNCHEZ
ROI SÁNCHEZ

Desarrollador en dev&del

Capitán en Hello, World!

Capaz de gestionar un proyecto informático E2E (de principio a fin).

Los discos de vinilo y los tatuajes son dos de sus mayores pasiones.