Como renderizar Markdown em streaming de redes neurais sem tremulação e travamentos
Quando você conecta pela primeira vez a saída em streaming de um modelo de linguagem ao frontend, um renderizador Markdown comum quase imediatamente transforma a página em um pesadelo. Bibliotecas padrão como markdown-it ou marked foram projetadas para documentos estáticos prontos. Se você alimentá-las com um stream de tokens bruto via SSE ou WebSocket, elas re-interpretam todo o texto em cada chunk, redesenham a árvore DOM e bagunçam o scroll.
A partir daqui, a interface começa a tremer de forma perceptível. O syntax highlighting pisca, blocos de código inacabados quebram a marcação abaixo, e fórmulas não fechadas ficam presas em um estado de carregamento infinito. O repositório markstream-vue resolve exatamente esse problema frustrante.
O que está sob o capô da biblioteca
O projeto começou como um componente especializado para Vue 3, mas com o tempo o autor dividiu a arquitetura em um core stream-markdown-parser e adaptadores para diferentes frameworks. Agora existem pacotes prontos para Vue 3, Nuxt, React, Next.js, Svelte 5, Angular e até o legado Vue 2.
A tarefa principal do renderizador é manter o DOM estável durante as frequentes micro-atualizações do texto. A biblioteca analisa o stream incrementalmente, entende estados intermediários de tags não fechadas e atualiza apenas os nós alterados, deixando o restante da página intacto.
Modos de operação e gerenciamento de carga
A biblioteca possui duas abordagens de renderização fundamentalmente diferentes que podem ser alternadas via prop mode.
O modo mode="chat" é projetado para chats de IA. Nele, o renderizador agrupa os tokens recebidos em pequenos lotes e os exibe com um efeito de digitação suave. Ao mesmo tempo, animações de opacidade desnecessárias são desativadas para que a interface não trema com cada nova palavra.
Se você precisa exibir um longread ou documentação gigante gerada, é melhor habilitar a virtualização via mode="docs". O renderizador mantém uma janela fixa de elementos na árvore DOM ativa (cerca de 220 nós por padrão). Isso mantém o consumo de memória do navegador em um nível estável e evita travamentos ao rolar conversas longas.
<script setup lang="ts">
import { ref } from 'vue'
import MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'
const message = ref('')
const isDone = ref(false)
// Получаем чанки через EventSource или fetch
const eventSource = new EventSource('/api/chat')
eventSource.onmessage = (event) => {
message.value += event.data
}
eventSource.addEventListener('done', () => {
isDone.value = true
eventSource.close()
})
</script>
<template>
<MarkdownRender
mode="chat"
:content="message"
:final="isDone"
smooth-streaming="auto"
:fade="false"
/>
</template>
A prop final é crítica ao trabalhar com um stream. Enquanto final="false" for true, o parser tolera calmamente construções cortadas no meio da palavra. Assim que o sinal de conclusão chega, o renderizador limpa o cache de streaming e traz a marcação à sua forma final.
Trabalhando com blocos complexos
Parsers comuns tropeçam em diagramas Mermaid ou fórmulas KaTeX se a sintaxe ainda não foi totalmente escrita. No markstream, esse momento é pensado nos mínimos detalhes.
Diagramas Mermaid e fórmulas
Dependências pesadas como mermaid e katex não estão incluídas no bundle principal. Você as instala como peer dependencies e as ativa chamando funções:
import { enableKatex, enableMermaid } from 'markstream-vue'
import 'katex/dist/katex.min.css'
enableMermaid()
enableKatex()
Diagramas Mermaid são analisados progressivamente. Se o gráfico ainda está sendo escrito pelo modelo, o renderizador mostra um placeholder elegante em vez de um erro de sintaxe no console. Para KaTeX, você pode descarregar a análise de fórmulas para um Web Worker separado via CDN, para que expressões matemáticas pesadas não bloqueiem a thread principal da interface.
Blocos de código e diffs
Na versão 2.0, os desenvolvedores abandonaram o pesado editor Monaco em favor da integração com stream-diffs. Agora você pode exibir diffs de arquivos interativos diretamente no stream, alternar entre temas claro e escuro e configurar alturas de blocos.
<template>
<MarkdownRender
:content="content"
:is-dark="true"
:code-block-props="{
theme: { light: 'vitesse-light', dark: 'vitesse-dark' }
}"
/>
</template>
Componentes Vue personalizados dentro do Markdown
Às vezes os modelos emitem tags não padrão, por exemplo <thinking> para uma cadeia de raciocínio ou shortcodes personalizados para chamar botões e widgets. Você pode interceptar esses e substituí-los por componentes Vue completos:
import { setCustomComponents } from 'markstream-vue'
setCustomComponents('chat-scope', {
CALLOUT: () => import('./components/Callout.vue'),
THINKING: () => import('./components/ThinkingAccordion.vue'),
})
No template, basta especificar o mesmo identificador:
<MarkdownRender
:content="message"
custom-id="chat-scope"
:custom-html-tags="['thinking']"
/>
Renderização no servidor e transferência de estado
Se você está construindo uma aplicação com Nuxt ou Next.js, não precisa executar o parser no cliente do zero. O documento pode ser analisado no servidor em uma estrutura de nós tipados:
import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue'
const md = getMarkdown()
const nodes = parseMarkdownToStructure(rawMarkdown, md, { final: true })
O componente cliente aceita nós prontos via prop :nodes="nodesFromServer", o que proporciona hidratação rápida sem mismatches de layout. Se você precisar continuar o streaming após o carregamento inicial da página, o cliente simplesmente retoma o buffer e continua analisando novos trechos.
Cenários práticos
A biblioteca cobre várias tarefas comuns de desenvolvimento frontend simultaneamente:
- Interfaces de diálogo com modelos de linguagem grandes, onde é importante eliminar tremulação e sacudidas de tela.
- Sistemas de revisão de código e geração de patches com exibição de diff durante a geração.
- Bases de conhecimento e painéis de changelog com carregamento dinâmico de seções e componentes interativos.
- Páginas de documentação técnica com fórmulas e diagramas complexos.
Resumo
Se o seu projeto exibe arquivos Markdown estáticos de uma pasta local, um markdown-it comprovado lidará com isso sem complicações desnecessárias. Mas se você está trabalhando com um stream de tokens ao vivo de um LLM, movendo uma interface de chat para a web, ou cansado de lutar contra lentidão ao renderizar respostas longas, a biblioteca definitivamente merece um lugar nas suas dependências.
Ela elimina dezenas de problemas não óbvios de streaming e economiza muito tempo em escrever suas próprias soluções alternativas em torno de parsers. Para um início rápido, você pode verificar o playground online oficial ou fazer deploy de um ambiente de teste via StackBlitz.
Projetos relacionados