Hoe je streaming Markdown van neurale netwerken rendert zonder flikkering en bevriezingen
Wanneer je voor het eerst streaming output van een taalmodel op de frontend aansluit, verandert een gewone Markdown-renderer de pagina vrijwel onmiddellijk in een nachtmerrie. Standaard bibliotheken zoals markdown-it of marked zijn ontworpen voor kant-en-klare statische documenten. Als je ze een ruwe token-stream via SSE of WebSocket voert, herparsen ze de hele tekst bij elke chunk, tekenen ze de DOM-boom opnieuw en verpesten ze het scrollen.
Op dit punt begint de interface merkbaar te flikkeren. Syntaxisaccentuering knippert, onvoltooide codeblokken breken de opmaak eronder, en niet-gesloten formules blijven hangen in een eindeloze laadstatus. De markstream-vue repository lost dit frustrerende probleem exact op.
Wat er onder de motorkap van de bibliotheek zit
Het project begon als een gespecialiseerde component voor Vue 3, maar na verloop van tijd splitste de auteur de architectuur op in een core stream-markdown-parser en adapters voor verschillende frameworks. Er zijn nu kant-en-klare pakketten voor Vue 3, Nuxt, React, Next.js, Svelte 5, Angular en zelfs de legacy Vue 2.
De hoofdtaak van de renderer is om de DOM stabiel te houden tijdens frequente micro-updates van de tekst. De bibliotheek parseert de stream incrementeel, begrijpt tussenliggende statussen van niet-gesloten tags en werkt alleen de gewijzigde nodes bij, waarbij de rest van de pagina onaangeroerd blijft.
Werkingsmodi en load management
De bibliotheek heeft twee fundamenteel verschillende renderingbenaderingen die kunnen worden geschakeld via de mode prop.
De mode="chat" modus is ontworpen voor AI-chats. Hierin groepeert de renderer inkomende tokens in kleine batches en geeft ze weer met een vloeiend type-effect. Tegelijkertijd worden onnodige opacity-animaties uitgeschakeld zodat de interface niet schudt met elk nieuw woord.
Als je een enorme gegenereerde longread of documentatie moet weergeven, kun je virtualisatie inschakelen via mode="docs". De renderer houdt een vast venster van elementen in de actieve DOM-boom (ongeveer 220 nodes standaard). Dit houdt het geheugengebruik van de browser op een stabiel niveau en voorkomt bevriezingen bij het scrollen door lange conversaties.
<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>
De final prop is cruciaal bij het werken met een stream. Zolang final="false" true is, verdraagt de parser rustig constructies die halverwege een woord zijn afgekapt. Zodra het completion-signaal arriveert, wist de renderer de streaming-cache en brengt de opmaak naar zijn definitieve vorm.
Werken met complexe blokken
Gewone parsers struikelen over Mermaid-diagrammen of KaTeX-formules als de syntaxis nog niet volledig is geschreven. In markstream is dit moment tot in het kleinste detail doordacht.
Mermaid-diagrammen en formules
Zware dependencies zoals mermaid en katex zijn niet opgenomen in de main bundle. Je installeert ze als peer dependencies en activeert ze door functies aan te roepen:
import { enableKatex, enableMermaid } from 'markstream-vue'
import 'katex/dist/katex.min.css'
enableMermaid()
enableKatex()
Mermaid-diagrammen worden progressief geparseerd. Als de grafiek nog wordt geschreven door het model, toont de renderer een nette placeholder in plaats van een syntaxisfout in de console. Voor KaTeX kun je het parsen van formules uitbesteden aan een aparte Web Worker via CDN, zodat zware wiskundige expressies de hoofdinterface-thread helemaal niet blokkeren.
Codeblokken en diffs
In versie 2.0 hebben de ontwikkelaars de zware Monaco-editor laten vallen ten gunste van integratie met stream-diffs. Nu kun je interactieve file diffs direct in de stream weergeven, schakelen tussen lichte en donkere thema's en blokhoogtes configureren.
<template>
<MarkdownRender
:content="content"
:is-dark="true"
:code-block-props="{
theme: { light: 'vitesse-light', dark: 'vitesse-dark' }
}"
/>
</template>
Aangepaste Vue-componenten binnen Markdown
Soms geven modellen niet-standaard tags uit, bijvoorbeeld <thinking> voor een redeneerketen of aangepaste shortcodes voor het aanroepen van knoppen en widgets. Je kunt deze onderscheppen en vervangen door volledige Vue-componenten:
import { setCustomComponents } from 'markstream-vue'
setCustomComponents('chat-scope', {
CALLOUT: () => import('./components/Callout.vue'),
THINKING: () => import('./components/ThinkingAccordion.vue'),
})
In de template hoef je alleen dezelfde identifier op te geven:
<MarkdownRender
:content="message"
custom-id="chat-scope"
:custom-html-tags="['thinking']"
/>
Server-side rendering en state-overdracht
Als je een app bouwt met Nuxt of Next.js, hoef je de parser niet vanaf nul op de client uit te voeren. Het document kan op de server worden geparseerd tot een structuur van getypte nodes:
import { getMarkdown, parseMarkdownToStructure } from 'markstream-vue'
const md = getMarkdown()
const nodes = parseMarkdownToStructure(rawMarkdown, md, { final: true })
De client component accepteert kant-en-klare nodes via de :nodes="nodesFromServer" prop, wat snelle hydratatie biedt zonder layout-mismatches. Als je de streaming moet voortzetten na de initiële paginalading, pakt de client eenvoudigweg de buffer op en parseert verdere nieuwe porties.
Praktische scenario's
De bibliotheek dekt tegelijkertijd verschillende veelvoorkomende frontend-ontwikkeltaken:
- Dialog interfaces met grote taalmodellen, waarbij het elimineren van flikkering en schermschudden belangrijk is.
- Code review en patch-generatiesystemen met diff-weergave direct tijdens het genereren.
- Kennisbanken en changelog-panelen met dynamische sectielading en interactieve componenten.
- Technische documentatiepagina's met formules en complexe diagrammen.
Samenvatting
Als je project statische Markdown-bestanden uit een lokale map weergeeft, zal een bewezen markdown-it dit zonder onnodige complicaties afhandelen. Maar als je werkt met een live token-stream van een LLM, een chatinterface naar het web verplaatst, of genoeg hebt van het bestrijden van lag bij het renderen van lange responses, verdient de bibliotheek zeker een plaats in je dependencies.
Het elimineert tientallen niet voor de hand liggende streaming-problemen en bespaart een hoop tijd op het schrijven van je eigen workarounds rond parsers. Voor een snelle start kun je de officiële online playground bekijken of een testomgeving deployen via StackBlitz.
Gerelateerde projecten