Gutenberg es el editor predeterminado de WordPress. El editor te permite crear y dar estilo al contenido utilizando bloques distintos para texto, imágenes, vídeo y otros elementos del sitio a través de una interfaz de arrastrar y soltar. Este enfoque mejora la flexibilidad y las capacidades de diseño de WordPress.

Esta guía explica cómo procesar el contenido de Gutenberg como HTML utilizando la API REST de WordPress en un sitio estático Next.js.

Requisitos previos

Para seguir adelante, necesitas

Recuperar contenido de Gutenberg mediante una API REST

Para interactuar con tu sitio de WordPress mediante programación y recuperar contenido estructurado en bloques de Gutenberg, utiliza la API REST de WordPress o el plugin WPGraphQL. Estas herramientas te permiten recuperar el contenido de WordPress en formato JSON.

Para habilitar el acceso a datos JSON a través de la API REST, ajusta la configuración de los enlaces permanentes de WordPress para que no sea «Simple». Esto permite el acceso a la API a través de una URL estructurada, como se indica a continuación:

https://yoursite.com/wp-json/wp/v2

Realizando peticiones API a esta URL, puedes recuperar mediante programación diversa información y realizar operaciones en tu sitio de WordPress. Por ejemplo, puedes obtener una lista de entradas enviando una solicitud GET a:

https://yoursite.com/wp-json/wp/v2/posts

Esto devolverá un objeto JSON que contiene información sobre las entradas de tu sitio de WordPress, incluyendo títulos, contenido, detalles del autor, etc.

Procesar bloques Gutenberg como HTML

Al recuperar entradas de un sitio de WordPress que utiliza el editor de Gutenberg, el contenido almacenado en la base de datos puede contener una mezcla de metadatos HTML y JSON para describir varios tipos de bloques, como citas y galerías. Por ejemplo:

<!-- wp:quote {"className":"inspirational-quote","style":{"typography":{"fontSize":"large"}}} -->
<blockquote class="wp-block-quote inspirational-quote has-large-font-size"><p>“The journey of a thousand miles begins with one step.”</p><cite>Lao Tzu</cite></blockquote>
<!-- /wp:quote -->

<!-- wp:gallery {"ids":[34,35],"columns":2,"linkTo":"none","sizeSlug":"medium","className":"custom-gallery"} -->
<ul class="wp-block-gallery columns-2 is-cropped custom-gallery"><li class="blocks-gallery-item"><figure><img src="http://example.com/wp-content/uploads/2021/09/image1-300x200.jpg" alt="A breathtaking view of the mountains" class="wp-image-34"/></figure></li><li class="blocks-gallery-item"><figure><img src="http://example.com/wp-content/uploads/2021/09/image2-300x200.jpg" alt="Serene lakeside at dawn" class="wp-image-35"/></figure></li></ul>
<!-- /wp:gallery -->

Este fragmento ilustra dos bloques Gutenberg: una Cita y una Galería. Cada uno está enriquecido con metadatos JSON encapsulados dentro de comentarios HTML. Los metadatos definen atributos como nombres de clases, estilos y otras configuraciones relevantes para la presentación del bloque.

Cuando obtienes estos bloques a través de la API REST de WordPress o WPGraphQL, WordPress los procesa, transformando la combinación de metadatos HTML y JSON en elementos HTML completamente renderizados que puedes incorporar directamente a las páginas web. El HTML transformado para los bloques anteriores tendría el siguiente aspecto:

<blockquote class="wp-block-quote inspirational-quote has-large-font-size"><p>“The journey of a thousand miles begins with one step.”</p><cite>Lao Tzu</cite></blockquote>

<ul class="wp-block-gallery columns-2 is-cropped custom-gallery">
  <li class="blocks-gallery-item"><figure><img loading="lazy" src="http://example.com/wp-content/uploads/2021/09/image1-300x200.jpg" alt="A breathtaking view of the mountains" class="wp-image-34" sizes="(max-width: 300px) 100vw, 300px" /></figure></li>
  <li class="blocks-gallery-item"><figure><img loading="lazy" src="http://example.com/wp-content/uploads/2021/09/image2-300x200.jpg" alt="Serene lakeside at dawn" class="wp-image-35" sizes="(max-width: 300px) 100vw, 300px" /></figure></li>
</ul>

Para los desarrolladores que construyen aplicaciones desacopladas o headless utilizando frameworks JavaScript como Next.js, esto presenta un método directo para mostrar contenido inyectando directamente el HTML en la página utilizando la propiedad dangerouslySetInnerHTML para renderizar el marcado.

<div dangerouslySetInnerHTML={{ __html: <raw_html_string> }} />

Además, puede que necesites realizar un formateo adicional para elementos como enlaces y manejar el exceso de caracteres de nueva línea ( \n ), que esta guía explica más adelante.

Procesar el contenido de los bloques de Gutenberg en el sitio estático Next.js

En esta sección, vamos a recuperar el contenido de WordPress en un proyecto Next.js y, a continuación, procesaremos los bloques Gutenberg como HTML.

  1. Empieza configurando una función para obtener entradas de tu sitio de WordPress. Abre el archivo src/page.js de tu proyecto y sustituye su contenido por el siguiente fragmento de código:
    const getWpPosts = async () => {
    	const res = await fetch('https://yoursite.com/wp-json/wp/v2/posts');
      	const posts = await res.json();
    	return posts;
    };

    Esta función asíncrona realiza una solicitud a la API REST de WordPress. Obtiene todas las entradas disponibles en tu sitio y las devuelve como un array.

  2. A continuación, vamos a utilizar las entradas obtenidas dentro de un simple componente de página Next.js registrando las entradas en la consola y mostrando un saludo básico:
    const page = async () => {
      const posts = await getWpPosts();
      console.log(posts);
      
      return (
        <div>
          <h1>Hello World</h1>
        </div>
      );
    };
    
    export default page;

    Cuando ejecutas tu proyecto utilizando npm run dev, muestra el mensaje «Hello World» y registra las entradas obtenidas en el Terminal.

    [
      {
        "_links" : {
          "about" : [...],
          "author" : [...],
          "collection" : [...],
          "curies" : [...],
          "predecessor-version" : [...],
          "replies" : [...],
          "self" : [...],
          "version-history" : [...],
          "wp:attachment" : [...],
          "wp:term" : [...]
        },
        "author" : 1,
        "categories" : [...],
        "comment_status" : "open",
        "content" : {
          "protected" : false,
          "rendered" : "\n<p>Fire, a primal force, captivates with its <strong>flickering flames</strong>, evoking both awe and caution. Its <quote>dance</quote> symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate.</p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"148\" src=\"https://img.example.com/wp-content/uploads/2024/02/burningbuilding.jpg\" alt=\"\" class=\"wp-image-14\"/></figure>\n\n\n\n<p>In ancient times, fire was a beacon of light and warmth, essential for survival. Today, it remains a symbol of human ingenuity and danger. From the comforting glow of a hearth to the destructive fury of wildfires, fire’s dual nature reminds us of our fragile relationship with the elements.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https://img.example.com/premium-photo/painting-burning-building-illuminated-by-bright-flames-night_168058-249.jpg?w=1380\" alt=\"\"/></figure>\n\n\n\n<p>You can check out other articles on our blog:</p>\n\n\n\n<ul>\n<li><a href=\"https://yoursite.com/?p=6\">Lorem Ipsum: Beginnings</a></li>\n\n\n\n<li><a href=\"https://yoursite.com/?p=9\">Lorem Ipsum: Act 2</a></li>\n\n\n\n<li><a href=\"https://yoursite.com/?p=11\">Lorem Ipsum: Act 3</a></li>\n</ul>\n"
        },
        "date" : "2024-02-27T12:08:30",
        "date_gmt" : "2024-02-27T12:08:30",
        "excerpt" : {
          "protected" : false,
          "rendered" : "<p>Fire, a primal force, captivates with its flickering flames, evoking both awe and caution. Its dance symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate. In ancient times, fire was a beacon of light and warmth, […]</p>\n"
        },
        "featured_media" : 0,
        "format" : "standard",
        "guid" : {
          "rendered" : "https://yoursite.com/?p=13"
        },
        "id" : 13,
        "link" : "https://yoursite.com/?p=13",
        "meta" : {
          "footnotes" : ""
        },
        "modified" : "2024-02-29T16:45:36",
        "modified_gmt" : "2024-02-29T16:45:36",
        "ping_status" : "open",
        "slug" : "fire-fire",
        "status" : "publish",
        "sticky" : false,
        "tags" : [],
        "template" : "",
        "title" : {
          "rendered" : "Fire"
        },
        "type" : "post"
       },
      },
      ...
    ]

    Los objetos JSON que representan los datos individuales de las entradas de Gutenberg incluyen varios campos, entre los cuales los campos de contenido y extracto se devuelven como bloques de Gutenberg procesados como cadenas HTML.

  3. Para representar correctamente este contenido HTML en Next.js, empleamos la propiedad dangerouslySetInnerHTML:
    const page = async () => {
      const posts = await getWpPosts();
    
      return (
        <>
          <h1> Headless Blog </h1>
    
          <div>
            {posts.map((post) => (
              <Link href={'/blog/' + post.id} key={post.id}>
                <h2>
                  {post.title.rendered} <span>-></span>
                </h2>
                <div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
              </Link>
            ))}
          </div>
        </>
      );
    };
    
    export default page;

    En este componente actualizado, iteramos sobre el array de entradas obtenidas para generar una lista de extractos de entradas. Cada extracto se envuelve en un componente Link para la navegación, mostrando el título de la entrada y un fragmento de su contenido.

    La propiedad dangerouslySetInnerHTML se utiliza para analizar y mostrar el contenido HTML del campo excerpt.rendered.

  4. A continuación, crea un archivo blog/[id]/page.js dentro del directorio de la aplicación. Las carpetas se utilizan para definir rutas. Así, al crear una carpeta blog, defines la ruta blog. Combina esto con el enrutamiento dinámico para generar rutas para cada entrada.
  5. Cada entrada tiene un ID. Utiliza este ID para generar su ruta única, /blog/{post_id}en tu aplicación. Añade el siguiente código:
    import Link from 'next/link';
    
    export async function generateStaticParams() {
        const res = await fetch('https://yoursite.com/wp-json/wp/v2/posts');
        const posts = await res.json();
        return posts.map((post) => {
            return {
                params: {
                    id: post.id.toString(),
                },
            };
        });
    }
    
    export async function getPost(id) {
        const response = await fetch('https://yoursite.com/wp-json/wp/v2/posts/' + id);
        const post = await response.json();
        return post;
    }

    La función generateStaticParams() genera estáticamente rutas en tiempo de compilación basándose en el ID correspondiente devuelto en cada entrada. La función getPost() obtiene los datos de Gutenberg de la API REST para la entrada con el ID introducido.

    En una sección anterior se ha mostrado un ejemplo de datos de Gutenberg analizados y devueltos por la API REST para una entrada. Por ahora, sólo nos interesa el campo content.rendered:

    [
      {
        ...
        "content": {
          "rendered" : "\n<p>Fire, a primal force, captivates with its <strong>flickering flames</strong>, evoking both awe and caution. Its <quote>dance</quote> symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate.</p>\n\n\n\n<figure> class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"148\" src=\"https://img.example.com/wp-content/uploads/2024/02/burningbuilding.jpg\" alt=\"\" class=\"wp-image-14\"/></figure>\n\n\n\n<p>In ancient times, fire was a beacon of light and warmth, essential for survival. Today, it remains a symbol of human ingenuity and danger. From the comforting glow of a hearth to the destructive fury of wildfires, fire’s dual nature reminds us of our fragile relationship with the elements.</p>\n\n\n\n<figure> class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https://img.example.com/premium-photo/painting-burning-building-illuminated-by-bright-flames-night_168058-249.jpg?w=1380\" alt=\"\"/></figure>\n\n\n\n<p>You can check out other articles on our blog:</p>\n\n\n\n<ul>\n<li><a> href=\"https://yoursite.com/?p=6\">Lorem Ipsum: Beginnings</a></li>\n\n\n\n<li><a> href=\"https://yoursite.com/?p=9\">Lorem Ipsum: Act 2</a></li>\n\n\n\n<li><a> href=\"https://yoursite.com/?p=11\">Lorem Ipsum: Act 3</a></li>\n</ul>\n"
        },
        ...
      }
    ]

    Este campo contiene el HTML sin procesar de la entrada. Puede renderizarse directamente utilizando la propiedad dangerouslySetInnerHTML de esta forma, <div dangerouslySetInnerHTML={{ __html: <raw_html_string> }} />.

  6. A continuación, puedes procesar los datos analizando los enlaces internos y redimensionando las imágenes. Instala el paquete html-react-parser para simplificar el proceso de procesamiento de etiquetas:
    npm install html-react-parser --save
  7. Añade el siguiente código al archivo blog/[id]/page.js:
    import parse, { domToReact } from "html-react-parser";
    
    /*
     * We use a regular expression (pattern) to match the specific URL you want to replace.
     * The (\d+) part captures the numeric ID after ?p=.
     * Then, we use the replacement string 'data-internal-link="true" href="/blog/$1"',
     * where $1 is a placeholder for the captured ID.
     */
    export function fixInternalLinks(html_string) {
      const pattern = /href="https:\/\/yoursite.com\/\?p=(\d+)"/g;
      const replacement = 'data-internal-link="true" href="/blog/$1"';
    
      return html_string.replace(pattern, replacement);
    }
    
    export function parseHtml(html) {
      // Replace 2+ sequences of '\n' with a single '<br />' tag
      const _content = html.replace(/\n{2,}/g, '<br />');
      const content = fixInternalLinks(_content);
    
      const options = {
        replace: ({ name, attribs, children }) => {
          // Convert internal links to Next.js Link components.
          const isInternalLink =
            name === "a" && attribs["data-internal-link"] === "true";
    
          if (isInternalLink) {
            return (
              <Link href={attribs.href} {...attribs}>
                {domToReact(children, options)}
              </Link>
        	  );
          } else if (name === "img") {
            attribs["width"] = "250";
            attribs["height"] = "150";
            return (
              <img {...attribs}/>
            );
          }
        },
      };
    
      return parse(content, options);
    }

    La función fixInternalLinks() utiliza una expresión regular para encontrar enlaces a entradas de tu sitio de WordPress a partir de la cadena HTML. En el HTML sin procesar, puedes ver que la entrada contiene una etiqueta List con enlaces a otras entradas de tu sitio, sustituyendo esos enlaces por enlaces internos a rutas de tu sitio estático.

    La función parseHTML() encuentra múltiples secuencias de exceso de nuevas líneas, n y las sustituye por etiquetas <br />. También encuentra enlaces internos y convierte las etiquetas de anclaje en etiquetas de enlace. A continuación, esta función redimensiona las imágenes utilizando los atributos de las etiquetas.

  8. Para generar la interfaz de usuario principal de cada ruta dinámica, añade el siguiente código:
    export default async function Post({ params }) {
      const post = await getPost(params.id);
    
      const content = parseHtml(post.content.rendered);
    
      return (
        <>
          <h1>
            {post.title.rendered}
          </h1>
     	 
          <div>{content}</div>
        </>
      );
    }

    Tras procesar el HTML sin procesar de los datos de Gutenberg, el código devuelve JSX que representa la interfaz de usuario formateada de la página.

Finalmente, cuando ejecutes tu proyecto, la página de inicio mostrará una lista de entradas de tu WordPress. Además, cuando hagas clic en entradas individuales, verás los contenidos de Gutenberg analizados correctamente.

Despliega tu sitio estático Next.js en Kinsta

Cuando se combina WordPress headless con frameworks de última generación como Next.js, resulta esencial encontrar una solución de despliegue rentable, especialmente si utilizas un potente Alojamiento de WordPress como el de Kinsta para tu sitio de WordPress. El servicio de Alojamiento de Sitios Estáticos de Kinsta ofrece una forma sencilla y asequible de publicar tu sitio online.

Kinsta te permite alojar hasta 100 sitios web estáticos de forma gratuita. En primer lugar, envía tu código a tu proveedor Git preferido (Bitbucket, GitHub o GitLab). Una vez que tu repositorio esté listo, sigue estos pasos para desplegar tu sitio estático en Kinsta:

  1. Inicia sesión o crea una cuenta para ver tu panel MyKinsta.
  2. Autoriza a Kinsta con tu proveedor de Git.
  3. Haz clic en Sitios Estáticos en la barra lateral izquierda, y luego en Añadir sitio.
  4. Selecciona el repositorio y la rama desde la que deseas desplegar.
  5. Asigna un nombre único a tu sitio.
  6. Añade la configuración de construcción en el siguiente formato:
    • Comando de construcción: npm run build
    • Versión de Node: 18.16.0
    • Directorio de publicación: out
  7. Por último, haz clic en Crear sitio.

¡Y ya está! En pocos segundos tendrás un sitio desplegado. Se proporciona un enlace para acceder a la versión desplegada de tu sitio. Si lo deseas, puedes añadir más adelante tu dominio personalizado y tu certificado SSL.

Como alternativa al alojamiento de sitios estáticos, puedes optar por desplegar tu sitio estático con el servicio de Alojamiento de Aplicaciones de Kinsta, que proporciona una mayor flexibilidad de alojamiento, una gama más amplia de ventajas y acceso a funciones más robustas, como escalabilidad, despliegue personalizado mediante un Dockerfile y analíticas exhaustivas que abarcan datos históricos y en tiempo real. Tampoco necesitas configurar tu proyecto Next.js para la renderización estática.

Resumen

Esta guía ha explicado cómo integrar y procesar el contenido de los bloques de Gutenberg eficazmente como HTML a través de la API de WordPress. Esto hace posible renderizar cualquier tipo de contenido en tu front-end cuando utilizas WordPress headless.

Puedes alojar tu WordPress headless en nuestro servicio de Alojamiento Administrado de WordPress y desplegar tu sitio estático en nuestro servicio de Alojamiento de Sitios Estáticos. Esto significa que todo lo relacionado con tu sitio está en un único panel de control: MyKinsta.

Al elegir Kinsta, te beneficias de un proveedor de alojamiento que prioriza el rendimiento y la escalabilidad óptimos del sitio, a la vez que fortalece firmemente los sitios web con medidas de seguridad avanzadas. ¡Prueba Kinsta hoy mismo!

¿Qué opinas sobre WordPress headless y su renderización? ¿Tienes una forma mejor de integrar los bloques Gutenberg? ¡Comparte tus ideas en la sección de comentarios!

Jeremy Holcombe Kinsta

Content & Marketing Editor at Kinsta, WordPress Web Developer, and Content Writer. Outside of all things WordPress, I enjoy the beach, golf, and movies. I also have tall people problems ;).