BlogPost
por: Jonathan Folland 6 de fevereiro de 2025

Renderização de Rich Text JSON no Astro.js com a API de entrega sem cabeça do Umbraco

Ao trabalhar com o Astro.js e a API de entrega sem cabeça do Umbraco, um desafio que os programadores podem enfrentar é a renderização dinâmica de conteúdo de texto rico armazenado como JSON. Ao contrário do conteúdo HTML tradicional, que pode ser injetado utilizando set:html, os dados JSON estruturados requerem uma abordagem mais flexível, especialmente quando é necessário integrar componentes personalizados para manipular elementos específicos.

O desafio

A diretiva set:html do Astro é uma forma comum de renderizar conteúdo HTML em bruto de forma segura. No entanto, quando se lida com JSON estruturado - como o umb-rte-elementdo Umbraco - estemétodo torna-se limitativo. Precisamos de uma forma de analisar a estrutura JSON e renderizar dinamicamente os componentes apropriados, mantendo a arquitetura baseada em componentes do Astro.

A solução

Para lidar com isso, podemos criar um componente recursivo do Astro que processa o JSON e renderiza os elementos apropriados usando componentes do Astro. Os tipos no código a seguir foram gerados usando o pacote Umbraco.Community.DeliveryApiExtensions, que ajuda a definir tipos fortes para a resposta JSON. Ao utilizar tipagem forte e extensões da comunidade como Umbraco.Community.DeliveryApiExtensions, garantimos um tratamento mais seguro e previsível do conteúdo baseado em JSON.

Exemplo de 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>
)}

Quebrando o código

  • RichTextField.astro recebe o bloco json de nível superior para o campo de rich text, recupera blocos se presentes e passa para o componente recursivo RenderRichText astro.
  • O componente do astro RenderRichText processa recursivamente a estrutura JSON.
  • Para elementos da etiqueta #text, o processo simplesmente escreve o texto. Utilizamos set:html num elemento Fragment para evitar blocos de texto com código de escape duplicado.
  • Para elementos nulos (hr, br, etc), o processo escreve o elemento juntamente com quaisquer atributos e uma etiqueta de fecho.
  • Para elementos HTML genéricos não vazios (div, p, ol, etc.), o processo escreve o elemento juntamente com quaisquer atributos e depois chama-se a si próprio recursivamente para renderizar quaisquer elementos filhos.
  • Finalmente, para tags de umb-rte-element, ele passa os dados do bloco associado para um RichTextFieldBlockItem que determina o componente astro que deve renderizar o bloco.

Porque é que isto é importante

Esta abordagem dá aos desenvolvedores Astro.js que trabalham com a API Headless Delivery do Umbraco uma maneira estruturada de renderizar rich text sem depender de set:html e permite consumir facilmente componentes astro regulares para renderização.

Conclusão

Se estiver a integrar a API de entrega sem cabeça do Umbraco com o Astro.js, o tratamento adequado do JSON de rich text é fundamental para uma experiência de desenvolvimento sem problemas. Com este método, pode manter uma estrutura limpa baseada em componentes enquanto renderiza conteúdo dinâmico de forma eficiente.

Partilhar


Aproveite o poder do headless para alcançar a excelência em marketing!

A equipe da Given Data LLC monitora continuamente os avanços no espaço de gerenciamento de conteúdo, mantendo-nos à frente da concorrência. Necessidade urgente? ligue para nós

+1 786-475-5504

Contate-nos Arrow Right 2

Serviços

Recursos

Oportunidades

Boletim

©2025 Given Data, LLC. Todos os direitos reservados.