Guia Prático: Renderizando Markdown e JSON no Front-end com Markedjs

Tutorial com passos estruturados

Guia Prático: Renderizando Markdown e JSON no Front-end com Markedjs

Introdução

A separação eficiente entre conteúdo e estrutura é uma premissa fundamental no desenvolvimento front-end moderno. Neste guia prático, você aprenderá a construir um site dinâmico que consome uma lista de conteúdos estruturada em JSON e renderiza arquivos Markdown de forma assíncrona, sem a necessidade de frameworks pesados.

Utilizando apenas HTML, CSS, Vanilla JavaScript e a biblioteca marked.js, desenvolveremos uma solução leve, modular e segura. Detalharemos cada etapa da implementação: desde a estruturação dos dados e requisições assíncronas com a Fetch API, até a geração da navegação dinâmica, captura de eventos de clique via delegação e a conversão do Markdown para HTML em tempo real. O resultado é uma arquitetura prática, ideal para documentações, portfólios ou blogs baseados em arquivos estáticos.

Conceitos Fundamentais

Antes de iniciarmos o código, é importante alinhar os pilares desta arquitetura:

  • JSON como Banco de Dados: Atuará como o índice do nosso site, armazenando metadados (título, descrição, data e caminho) de cada publicação.
  • Markdown para Conteúdo: Arquivos .md armazenarão o texto bruto, facilitando a escrita e a manutenção sem misturar formatação com código HTML.
  • Renderização Sob Demanda (Lazy Loading lógico): O conteúdo de um artigo só é baixado e processado pelo navegador quando o usuário clica no link, economizando banda e processamento na carga inicial da página.

Pré-requisitos

Para acompanhar este manual, você precisará de:

  • Conhecimento básico de HTML e JavaScript (ES6+).
  • Um editor de código (como o VS Code).
  • Um servidor web local (como a extensão Live Server do VS Code) para evitar bloqueios de CORS ao realizar requisições fetch em arquivos locais.

Passo a Passo da Implementação

Passo 1: Estrutura do Projeto

Organize os arquivos do seu projeto com a seguinte hierarquia para manter a separação clara entre lógica, dados e conteúdo:

/meu-projeto
 ├── index.html
 ├── app.js
 ├── dados/
 │    └── artigos.json
 └── conteudo/
      ├── primeiro-artigo.md
      └── segundo-artigo.md

Passo 2: O Arquivo de Índice (JSON) e o Conteúdo (Markdown)

Crie o arquivo dados/artigos.json. Ele será o array de objetos que alimentará nossa interface:

[
  {
    "titulo": "Arquitetura Front-end",
    "descricao": "Como separar dados de marcação de forma eficiente.",
    "data": "2026-08-10",
    "link": "../conteudo/primeiro-artigo.md"
  },
  {
    "titulo": "Segurança no DOM",
    "descricao": "Prevenindo ataques XSS ao manipular o DOM.",
    "data": "2026-08-15",
    "link": "../conteudo/segundo-artigo.md"
  }
]

Em seguida, crie um arquivo de exemplo em conteudo/primeiro-artigo.md:

# Arquitetura Front-end
Esta é uma demonstração de como o **marked.js** processa listas e formatações:
* Item 1
* Item 2

Passo 3: Estrutura Base em HTML

No arquivo index.html, vamos importar a biblioteca marked.js via CDN, referenciar nosso script e criar os contêineres onde o JavaScript injetará os dados.

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog Dinâmico com Markdown</title>
    <!-- Importação da biblioteca marked.js -->
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; padding: 20px; max-width: 800px; margin: auto; }
        .artigo-card { border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 5px; }
        #markdown { margin-top: 30px; padding-top: 20px; border-top: 2px solid #333; }
    </style>
</head>
<body>

    <h1>Publicações Recentes</h1>
    <!-- Área onde a lista do JSON será renderizada -->
    <div id="conteudo"></div>

    <!-- Área onde o conteúdo do Markdown será exibido -->
    <div id="markdown"></div>

    <!-- Importação da nossa lógica -->
    <script src="app.js"></script>
</body>
</html>

Passo 4: Lógica de Aplicação (JavaScript)

No arquivo app.js, vamos construir a lógica em blocos funcionais.

1. Configuração e Inicialização: Definimos o caminho do nosso “banco de dados” e iniciamos o consumo.

const arquivoIndice = "dados/artigos.json";

// Inicia a aplicação buscando o JSON
fetch(arquivoIndice)
  .then((response) => {
      if (!response.ok) throw new Error("Erro ao carregar o JSON");
      return response.text();
  })
  .then((resultado) => listar(resultado))
  .catch((error) => console.error("Falha na inicialização:", error));

2. Conversão de Caminhos Relativos para Absolutos: Para garantir que o fetch encontre os arquivos .md independentemente de onde a página foi aberta, criamos uma função utilitária:

const obterCaminhoCompleto = (caminhoRelativo) => {
  const caminhoPaginaAtual = window.location.href;
  return new URL(caminhoRelativo, caminhoPaginaAtual).href;
};

3. Renderização Segura da Lista (Mitigação de XSS): Esta função recebe a string JSON, converte em objeto e cria os elementos HTML.

function listar(jsonString) {
  const content = document.querySelector("#conteudo");
  const lista = JSON.parse(jsonString);

  lista.forEach((item) => {
    // Criação dos elementos
    const container = document.createElement("div");
    container.className = "artigo-card";

    const titulo = document.createElement("h4");
    titulo.textContent = item.titulo;

    const descricao = document.createElement("p");
    descricao.textContent = item.descricao;

    const data = document.createElement("p");
    data.textContent = "Publicado em: " + item.data;

    const link = document.createElement("a");
    link.href = obterCaminhoCompleto(item.link);
    link.textContent = "Ler artigo completo";
    
    // Montagem da estrutura
    container.append(titulo, descricao, data, link);
    content.append(container);
  });
}

Nota Arquitetural de Segurança: Optamos pelo uso de document.createElement e .textContent em vez de concatenar strings com .innerHTML +=. Se os dados do JSON vierem de fontes externas (como uma API), o uso de .innerHTML abre margem para ataques de XSS (Cross-Site Scripting). O textContent garante que o conteúdo seja interpretado estritamente como texto puro. Além disso, o método .append() oferece melhor performance na renderização de listas longas.

4. Consumo e Conversão do Markdown: Quando o usuário clicar em um link, esta função buscará o arquivo físico e delegará a conversão para a biblioteca marked.js.

function mostrarMark(caminhoMarkdown) {
  const visual = document.querySelector("#markdown");
  visual.innerHTML = "<p>Carregando...</p>"; // Feedback visual

  fetch(caminhoMarkdown)
    .then((response) => {
        if (!response.ok) throw new Error("Erro ao carregar o arquivo Markdown");
        return response.text();
    })
    .then((resultado) => {
        // O marked.parse converte a string markdown para tags HTML válidas
        visual.innerHTML = marked.parse(resultado);
    })
    .catch((error) => {
        console.error(error);
        visual.innerHTML = "<p>Erro ao carregar o conteúdo.</p>";
    });
}

Atenção: Ao contrário da função listar, aqui utilizamos .innerHTML intencionalmente, pois o objetivo do marked.parse() é gerar marcação HTML. Caso os arquivos Markdown sejam gerados por usuários (UGC), é imperativo sanitizar a saída com bibliotecas como o DOMPurify antes da injeção no DOM.

5. Interceptação de Cliques via Delegação de Eventos: Em vez de atrelar um evento de clique (addEventListener) a cada link individualmente durante a criação, aplicamos o conceito de Delegação de Eventos no documento global.

document.addEventListener('click', function (event) {
  // Verifica se o elemento clicado é um link DENTRO da nossa div de conteúdo
  if (event.target.tagName === 'A' && event.target.closest('#conteudo')) {
    event.preventDefault(); // Impede o redirecionamento padrão da página
    const urlAlvo = event.target.getAttribute('href');
    mostrarMark(urlAlvo);
  }
});

Por que delegar? A delegação garante que a interceptação funcione para elementos que foram injetados no DOM dinamicamente após o carregamento inicial da página. Além de manter o código mais limpo, exige menos memória do navegador, pois requer apenas um listener ativo.


Erros Comuns e Validações

  • Bloqueio por CORS (Cross-Origin Resource Sharing): Se você abrir o arquivo index.html com um duplo clique direto no navegador (usando o protocolo file://), o método fetch() falhará por questões de segurança do navegador. Solução: Execute o projeto através de um servidor local. No VS Code, instale a extensão “Live Server” e clique em “Go Live”.
  • Captura de links indesejados: O uso estrito da condição event.target.closest('#conteudo') na delegação de eventos é vital. Sem ele, a lógica interceptaria cliques em links do rodapé ou do menu principal de navegação, quebrando a usabilidade do restante do site.
  • Caminhos de Arquivo Inválidos: Certifique-se de que a relação entre o local do index.html e os caminhos declarados dentro do seu artigos.json esteja perfeitamente alinhada. A função obterCaminhoCompleto baseia-se na URL atual para fazer essa resolução.

Conclusão

Neste tutorial, aprendemos a estabelecer uma arquitetura front-end robusta e livre de dependências pesadas, listando informações a partir de uma base de dados JSON e exibindo-as de forma segura no DOM. Ao combinar a delegação de eventos com requisições assíncronas e o poder de conversão da biblioteca marked.js, você implementou o núcleo funcional de um gerador de sites estáticos. Esse padrão arquitetural (dados estruturados + conteúdo formatado + renderização sob demanda) proporciona alta performance, facilita a manutenção a longo prazo e pode ser facilmente escalado para projetos maiores, integrações com APIs reais ou sistemas de gestão de conteúdo headless (Headless CMS).

Comentários

Seja o primeiro a comentar!

Deixe seu comentário