BlogPost
por: Jonathan Folland 6 de febrero de 2025

Renderizado de texto enriquecido JSON en Astro.js con Umbraco Headless Delivery API

Al trabajar con Astro.js y la API Headless Delivery de Umbraco, uno de los retos a los que pueden enfrentarse los desarrolladores es la representación dinámica de contenido de texto enriquecido almacenado como JSON. A diferencia del contenido HTML tradicional que se puede inyectar mediante set:html, los datos JSON estructurados requieren un enfoque más flexible, especialmente cuando se necesita integrar componentes personalizados para manejar elementos específicos.

El reto

La directiva set: html de Astro es una forma habitual de renderizar contenido HTML sin procesar de forma segura. Sin embargo, cuando se trata de JSON estructurado, como el elemento umb-rtede Umbraco, este método se vuelve limitante. Necesitamos una forma de analizar la estructura JSON y renderizar dinámicamente los componentes apropiados manteniendo la arquitectura basada en componentes de Astro.

La solución

Para manejar esto, podemos crear un componente Astro recursivo que procese el JSON y renderice los elementos apropiados usando componentes Astro. Los tipos en el siguiente código fueron generados usando el paquete Umbraco.Community.DeliveryApiExtensions, que ayuda a definir tipos fuertes para la respuesta JSON. Usando tipos fuertes y extensiones de la comunidad como Umbraco.Community.DeliveryApiExtensions, aseguramos un manejo más seguro y predecible del contenido basado en JSON.

Ejemplo Json:

"blogRichText": {
            "tag": "#root",
            "attributes": {},
            "elements": [
                {
                    "tag": "p",
                    "attributes": {
                        "id": "someId",
                        "class": "some-class"
                    },
                    "elements": [
                        {
                            "text": "This is ",
                            "tag": "#text"
                        },
                        {
                            "tag": "strong",
                            "attributes": {},
                            "elements": [
                                {
                                    "text": "bold text",
                                    "tag": "#text"
                                }
                            ]
                        },
                        {
                            "text": " with an object following",
                            "tag": "#text"
                        }
                    ]
                },
                {
                    "tag": "umb-rte-block",
                    "attributes": {
                        "content-id": "705a6633-3e20-4f3a-be76-4b450a397608"
                    },
                    "elements": []
                },
                {
                    "tag": "p",
                    "attributes": {},
                    "elements": [
                        {
                            "text": "with some more text",
                            "tag": "#text"
                        }
                    ]
                }
            ],
            "blocks": [
                {
                    "content": {
                        "contentType": "angularCounter",
                        "id": "705a6633-3e20-4f3a-be76-4b450a397608",
                        "properties": {}
                    },
                    "settings": null
                }
            ]
        }

RichTextField.astro

---
import type { RichTextGenericElementModel , RichTextRootElementModel , RichTextTextElementModel  } from"@/api/umbraco";
import {richTextFieldHasContent} from"./RichTextHelper"import RenderRichText from'./RenderRichText.astro';

interface Props {
  richTextField: RichTextGenericElementModel | RichTextRootElementModel | RichTextTextElementModel | null | undefined;
}

const { richTextField } = Astro.props;

if (!richTextFieldHasContent(richTextField)) returnnull;
// If the field is the root element, grab the blocks for use in block elements.let blocks: any[] = [];
if (richTextField && richTextField.tag === '#root') {
  const root = richTextField as RichTextRootElementModel;
  blocks = root.blocks;
}
---
<divclass="rich-text-block container mx-auto px-[15px]"><RenderRichTextnode={richTextField}blocks={blocks}></div>

RenderRichText.astro:

---
// filepath: /src/components/richText/RenderRichText.astro
import RichTextFieldBlockItem from './RichTextFieldBlockItem.astro';
import type { ApiBlockItemModel, RichTextGenericElementModel, RichTextRootElementModel, RichTextTextElementModel } from "@/api/umbraco";
import RenderRichTextComponent from './RenderRichText.astro';

interface Props {
  node: RichTextGenericElementModel | RichTextRootElementModel | RichTextTextElementModel | null | undefined;
  blocks: ApiBlockItemModel[] | null | undefined;
}

const { node, blocks } = Astro.props;

if (!node) return null;

const voidElements = [
  "area",
  "base",
  "br",
  "col",
  "embed",
  "hr",
  "img",
  "input",
  "link",
  "meta",
  "param",
  "source",
  "track",
  "wbr"
];

const isText = node.tag === '#text';
const textNode = isText ? nodeas RichTextTextElementModel : null;
const isRoot = node.tag === '#root';
const rootNode = isRoot ? nodeas RichTextRootElementModel : null;
const isBlock = node.tag === 'umb-rte-block';
const blockNode = isBlock ? nodeas RichTextGenericElementModel : null;
const block = isBlock ? blocks?.find((b) => b.content && b.content.id === blockNode?.attributes['content-id']) : null;

const isGeneric = !isText && !isRoot && !isBlock;
const genericNode = isGeneric ? nodeas RichTextGenericElementModel : null;
const GenericTag = genericNode?.tag|| 'div';
const isVoidElement = voidElements.includes(GenericTag);
---

{isText && <Fragment set:html={textNode?.text}></Fragment>}

{isRoot && rootNode?.elements.map((child, i) => 
  <RenderRichTextComponent node={child} blocks={blocks} />)}

{isBlock && <RichTextFieldBlockItem block={block} />}

{isGeneric && isVoidElement && (
  <GenericTag {...genericNode?.attributes} />
)}

{isGeneric && !isVoidElement && (
  <GenericTag {...genericNode?.attributes}>
    {genericNode?.elements.map((child, i) => 
    )}
  </GenericTag>
)}

Desglose del código

  • RichTextField.astro recibe el bloque json de nivel superior para el campo de texto enriquecido, recupera los bloques si están presentes y los pasa al componente astro recursivo RenderRichText.
  • El componente astro RenderRichText procesa recursivamente la estructura JSON.
  • Para los elementos de la etiqueta #text, el proceso simplemente escribe el texto. Utilizamos set:html en un elemento Fragment para evitar bloques de texto con código de escape duplicado.
  • Para elementos void (hr, br, etc), el proceso escribe el elemento junto con cualquier atributo y una etiqueta de cierre.
  • Para elementos HTML genéricos no vacíos (div, p, ol, etc.), el proceso escribe el elemento junto con cualquier atributo y luego se llama a sí mismo recursivamente para renderizar cualquier elemento hijo.
  • Por último, para las etiquetas umb-rte-element, pasa los datos del bloque asociado a un RichTextFieldBlockItem que determina el componente astro que debe renderizar el bloque.

Por qué es importante

Este enfoque proporciona a los desarrolladores de Astro.js que trabajan con la API Headless Delivery de Umbraco una forma estructurada de renderizar texto enriquecido sin depender de set:html y permite consumir fácilmente componentes astro normales para la renderización .

Conclusión

Si estás integrando la API Headless Delivery de Umbraco con Astro.js, el manejo adecuado de texto enriquecido JSON es clave para una experiencia fluida del desarrollador. Con este método, puedes mantener una estructura limpia basada en componentes mientras renderizas contenido dinámico eficientemente.

Compartir


¡Aproveche el poder del headless para alcanzar la excelencia en marketing!

El equipo de Given Data LLC monitorea continuamente los avances en el espacio de gestión de contenido, lo que nos mantiene por delante de la competencia. ¿Necesidad urgente? llamanos

+1 786-475-5504

Contáctenos Arrow Right 2

Servicios

Recursos

Oportunidades

Boletín

©2025 Given Data, LLC. Reservados todos los derechos.