---
title: "Comark vs vue-markdown-render"
description: "Compare Comark with vue-markdown-render and @crazydos/vue-markdown for Vue 3. See how each renders Markdown, maps elements to Vue components, handles raw HTML, and streams AI output."
canonical_url: "https://comark.dev/compare/comark-vs-vue-markdown-render"
---
# Comark vs vue-markdown-render

> Compare Comark with vue-markdown-render and @crazydos/vue-markdown for Vue 3. See how each renders Markdown, maps elements to Vue components, handles raw HTML, and streams AI output.

**TL;DR**: [`vue-markdown-render`](https://github.com/cloudacy/vue-markdown-render) is a small wrapper that renders markdown-it HTML into a `<div>`. [`@crazydos/vue-markdown`](https://github.com/shunnNet/vue-markdown) brings the react-markdown model to Vue, with unified plugins and scoped slots per tag. Comark's [`@comark/vue`](https://comark.dev/rendering/vue) renders Vue components from a component syntax in the content, streams AI output, and has a Nuxt module.

## At a glance

|                               | Comark                                      | vue-markdown-render                    | @crazydos/vue-markdown                                        |
| ----------------------------- | ------------------------------------------- | -------------------------------------- | ------------------------------------------------------------- |
| Parser                        | markdown-exit (markdown-it plugin API)      | markdown-it 14                         | unified (remark and rehype)                                   |
| Output                        | Vue VNodes                                  | HTML string set as `innerHTML`         | Vue VNodes                                                    |
| Restyle elements              | `components` map, `Prose` prefix            | markdown-it plugins                    | Scoped slots per tag, `customAttrs`                           |
| Vue components in the content | `::alert{type="info"}` syntax               | No                                     | `<slot slot-name="...">` with `rehype-raw` and `sanitize` off |
| GFM tables and task lists     | Built in                                    | Tables built in, task lists via plugin | Via `remark-gfm`                                              |
| Raw HTML default              | Parsed into nodes                           | Escaped (`html: false`)                | Shown as text until you add `rehype-raw`                      |
| Streaming                     | `streaming` and `caret` props, `autoClose`  | No                                     | No                                                            |
| Async rendering               | `<Markdown>` is async, wrap in `<Suspense>` | Synchronous                            | `VueMarkdownAsync` for async plugins                          |
| Plugins                       | Comark plugins, markdown-it plugins         | markdown-it plugins                    | remark and rehype plugins                                     |

## Three rendering models

**vue-markdown-render** calls markdown-it and sets the result as the `innerHTML` of a `<div>`. It accepts markdown-it options and plugins:

```vue [vue-markdown-render]
<script setup lang="ts">
import VueMarkdown from 'vue-markdown-render'
import MarkdownItAnchor from 'markdown-it-anchor'

const plugins = [MarkdownItAnchor]
</script>

<template>
  <vue-markdown :source="markdown" :plugins="plugins" />
</template>
```

Because the output is an HTML string, Vue can't mount components inside it. It inherits markdown-it's safe defaults: raw HTML is off, and `validateLink` rejects `javascript:` and similar URLs. If you set `html: true`, markdown-it recommends an external sanitizer.

**@crazydos/vue-markdown** renders VNodes from a unified pipeline. You replace an element with a scoped slot, and you render its `children` with `<component :is>`:

```vue [@crazydos/vue-markdown]
<template>
  <VueMarkdown :markdown="markdown">
    <template #h2="{ children, ...props }">
      <h2 v-bind="props" class="title">
        <component :is="children" />
      </h2>
    </template>
  </VueMarkdown>
</template>
```

**Comark** parses to a document and renders each node as a VNode. The `components` prop maps a tag to a Vue component:

```vue [Comark]
<script setup lang="ts">
import { Markdown } from '@comark/vue'
import Heading from './components/Heading.vue'
</script>

<template>
  <Suspense>
    <Markdown :value="markdown" :components="{ h2: Heading }" />
  </Suspense>
</template>
```

Comark looks up `ProseH2`, then `H2`, then `h2`, then globally registered components. In Nuxt, the `@comark/nuxt` module registers the components in `~/components/prose` for you.

## Components in the content

`@crazydos/vue-markdown` supports a `<slot slot-name="...">` tag in the Markdown, filled from the parent with `<template #s-name>`. It requires `rehype-raw` and `:sanitize="false"`, and it doesn't support Vue template syntax such as `v-bind`.

Comark makes components part of the [syntax](https://comark.dev/syntax/components). Props, typed values, and named slots are text, and the renderer maps each `#slot` to a Vue named slot:

```mdc [content.md]
::card{title="Release notes" :version="2"}
The new parser is live.

#footer
Read the [changelog](https://example.com/changelog).
::
```

```vue [components/Card.vue]
<script setup lang="ts">
defineProps<{ title?: string, version?: number }>()
</script>

<template>
  <div class="card">
    <h3>{{ title }} v{{ version }}</h3>
    <slot />
    <footer><slot name="footer" /></footer>
  </div>
</template>
```

## Streaming

Neither Vue alternative documents a streaming mode. Comark's renderer accepts the growing text and completes unterminated syntax with [`autoClose`](https://comark.dev/reference/auto-close):

```vue [Message.vue]
<template>
  <Suspense>
    <Markdown :value="part.text" :streaming="part.state === 'streaming'" caret />
  </Suspense>
</template>
```

The `part` object comes from `@ai-sdk/vue`. See the [Nuxt AI SDK example](https://comark.dev/examples/ai/nuxt-ai-sdk) and [streaming in Vue](https://comark.dev/rendering/vue#streaming).

## What the alternatives do well

- **vue-markdown-render is small and synchronous**: it adds little on top of markdown-it and needs no `<Suspense>`. Comark's parser bundle is larger, because it includes markdown-exit, a YAML parser, an HTML parser, and entity tables.
- **@crazydos/vue-markdown uses unified**: remark and rehype plugins work as-is. Comark has no remark or rehype plugin support.
- **Safe raw HTML handling**: both escape or hide raw HTML by default. Comark parses raw HTML and keeps `<script>`, so add the [security plugin](https://comark.dev/plugins/built-in/security) with a `blockedTags` list for untrusted input.

If you render trusted, static Markdown and don't need components, vue-markdown-render is enough. If you rely on remark plugins, @crazydos/vue-markdown fits. Choose Comark for components in the content, streaming, or a Nuxt integration.

## FAQ

::accordion
  :::accordion-item{label="Can I use my markdown-it plugins with Comark?"}
  Yes. Wrap each plugin with `defineComarkPlugin` and pass it in `markdownItPlugins`. See [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it).
  :::

  :::accordion-item{label="Why does Comark's Markdown component need Suspense?"}
  `<Markdown>` parses asynchronously. To skip parsing in the browser, parse on the server and render the document with `<MarkdownDocument>`. It ships no parser to the client. See [`MarkdownDocument`](https://comark.dev/rendering/vue#code-markdowndocument).
  :::

  :::accordion-item{label="Does Comark work with Nuxt and Nuxt UI?"}
  Yes. The `@comark/nuxt` module auto-imports the components and registers prose overrides. It uses Nuxt UI prose components when `@nuxt/ui` is installed. See [Nuxt](https://comark.dev/rendering/nuxt).
  :::

  :::accordion-item{label="Is raw HTML in model output safe with Comark?"}
  Not by default. Markdown link syntax with a `javascript:` URL doesn't become a link, and the Vue renderer never forwards `innerHTML`, but the default `html` plugin keeps `<script>`. Add `security()` from `@comark/vue/plugins/security` with a `blockedTags` list.
  :::
::

---

- [Render in Vue](https://comark.dev/rendering/vue)
- [Render in Nuxt](https://comark.dev/rendering/nuxt)


## Sitemap

See the full [sitemap](https://comark.dev/sitemap.md) for all pages.
