Migrate from @nuxtjs/mdc to Comark
@nuxtjs/mdc was Nuxt-only. Comark is its successor — the markdown syntax is fully compatible, your .md files need no changes. What changes is the JavaScript API.
The migration has two parts: Core Package (programmatic API) and Nuxt Module (components, slots, config).
Quick Overview
- Package:
@nuxtjs/mdc→comark(core only) or@comark/nuxt(Nuxt module) - Parse:
parseMarkdown()→parse()· factory:createParse()(sync, no await) - Render:
stringifyMarkdown()→renderMarkdown()fromcomark/render - AST: object tree → compact tuples
['tag', props, ...children] - Result:
result.body/result.data→tree.nodes/tree.frontmatter - Renderer:
<MDCRenderer :body :data>→<ComarkRenderer :tree> - All-in-one:
<MDC :value>→<Comark :markdown> - Slots:
<MDCSlot />→ native<slot /> - Plugins: global
nuxt.config→ per-componentdefineComarkComponent({ plugins }) - Markdown files: no changes needed
Core Package
API Mapping
@nuxtjs/mdc |
comark |
|---|---|
parseMarkdown(md, opts) |
parse(md, opts) from comark |
createMarkdownParser(opts) (async) |
createParse(opts) (sync — no await) |
stringifyMarkdown(body, data) |
renderMarkdown(tree) from comark/render |
result.body (MDCRoot) |
tree.nodes (ComarkNode[]) |
result.data |
tree.frontmatter |
result.data.title |
tree.frontmatter.title |
result.toc |
tree.meta.toc (requires toc plugin) |
result.excerpt |
tree.meta.summary (requires summary plugin) |
AST Format
@nuxtjs/mdc |
comark |
|---|---|
{ type: 'root', children: MDCNode[] } |
{ nodes: ComarkNode[], frontmatter: {}, meta: {} } |
{ type: 'element', tag: 'p', props: {}, children: [] } |
['p', {}, ...children] |
{ type: 'text', value: 'hello' } |
'hello' (plain string) |
Parse Options
// Before — MDCParseOptions
{
remark: { plugins: { /* record */ } },
rehype: { options: {...}, plugins: { /* record */ } },
highlight: { theme: '...', langs: [...] } | false,
toc: { depth: 3, searchDepth: 2 } | false,
}
// After — ParseOptions
{
plugins: ComarkPlugin[], // ordered array, not a record
autoUnwrap: true, // removes <p> from single-paragraph containers
autoClose: true, // completes incomplete syntax (useful for streaming)
html: true, // parse embedded HTML tags
}
Plugins
The unified/remark/rehype pipeline is replaced by Comark's own lighter plugin interface.
| Feature | Before | After |
|---|---|---|
| Syntax highlighting | rehypeHighlight via createMarkdownParser |
highlight() from comark/plugins/highlight |
| Table of Contents | parseMarkdown(md, { toc: { depth: 3 } }) |
toc({ depth: 3 }) plugin |
| Excerpt / Summary | result.excerpt (built-in) |
summary() plugin → tree.meta.summary |
| Emoji | remark-emoji (enabled by default) |
emoji() plugin (opt-in) |
Available plugins: comark/plugins/toc, comark/plugins/highlight, comark/plugins/emoji, comark/plugins/task-list, comark/plugins/summary, comark/plugins/security, comark/plugins/alert, comark/plugins/math, comark/plugins/mermaid, comark/plugins/punctuation
Nuxt Module
Configuration
// Before
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: { highlight: { ... }, remarkPlugins: { ... } },
})
// After
export default defineNuxtConfig({
modules: ['@comark/nuxt'],
// No plugin config here — plugins are defined per-component
})
@comark/nuxt auto-imports: Comark, ComarkRenderer, defineComarkComponent, defineComarkRendererComponent.
Components
@nuxtjs/mdc |
@comark/nuxt |
|---|---|
<MDCRenderer :body :data :components> |
<ComarkRenderer :tree :components> |
<MDC :value :parser-options> |
<Comark :markdown :options> or <Comark>{{ md }}</Comark> |
<MDCSlot /> |
<slot /> |
<MDCSlot unwrap="p" /> |
<slot unwrap="p" /> |
<slot mdc-unwrap="p" /> |
<slot unwrap="p" /> |
For a pre-parsed tree, use <ComarkRenderer> directly instead of <Comark>.
<ComarkRenderer> props changes
MDC <MDCRenderer> |
Comark <ComarkRenderer> |
Notes |
|---|---|---|
body (MDCRoot) |
tree (ComarkTree) |
Different AST shape |
data |
— | Frontmatter is in tree.frontmatter |
tag |
— | Wrapper is always <div class="comark-content"> |
prose |
— | Prose* resolution is automatic |
unwrap |
— | Use autoUnwrap in parse options |
components |
components |
Same purpose |
| — | componentsManifest |
New: dynamic async component resolver |
| — | streaming |
New: streaming mode |
| — | caret |
New: animated caret for streaming |
Summary rendering
<!-- Before -->
<MDCRenderer :body="result.excerpt ?? result.body" :data="result.data" />
<!-- After -->
<Comark summary>{{ markdown }}</Comark>
defineComarkComponent
Replaces global mdc: { ... } config. Define reusable components with their own plugins and component mappings:
import { defineComarkComponent } from '@comark/vue'
import highlight from 'comark/plugins/highlight'
import toc from 'comark/plugins/toc'
export const ArticleComark = defineComarkComponent({
name: 'ArticleComark',
plugins: [highlight({ themes: { light: githubLight, dark: githubDark } }), toc()],
components: { alert: CustomAlert },
})
Slots
<MDCSlot /> → native <slot />. Named slots work the same (#slotName in markdown, <slot name="slotName"> in component). The unwrap attribute (<slot unwrap="p">) strips wrapper tags from children.
Prose Components
Same Prose*.vue naming convention in components/prose/. Resolution changed from kebab-case (prose-p) to PascalCase (ProseP) internally — no file changes needed.
Nuxt UI Integration
When using Nuxt UI, @comark/nuxt registers Nuxt UI prose components automatically. Shorthand callout components are available:
::note — informational
::tip — helpful suggestion
::warning — something to watch out for
::caution — critical warning
These are only available with Nuxt UI. Without it, use ::callout{icon="..." color="..."}.
Common Pitfalls
createParseis sync — noawaitneeded (unlikecreateMarkdownParser)- Attribute naming: Comark uses
attrs.lang, notattrs.language - Frontmatter: stored in
tree.frontmatter, not passed separately renderMarkdownincludes frontmatter — readstree.frontmatterautomatically- No
unifiedpipeline —mdc.config.tshooks (pre,remark,rehype,post) have no equivalent, useComarkPlugininterface instead - Emoji is opt-in — not enabled by default like in MDC's
remark-emoji
Component Syntax
The MDC block and inline component syntax is identical — no changes needed in .md files.
Unsupported Features
- Binding syntax (
{{ variable }}) — not supported, rendered as plain text - Props binding / data passing — no equivalent for
parseMarkdown(md, { data: { ... } })
Source: comarkdown/comark — distributed by TomeVault.