| name | migrate-mdc-to-comark |
| description | Migrate a Nuxt project from @nuxtjs/mdc to Comark. Covers package changes, parse/render API mapping, AST format, Nuxt module config, components, slots, plugins, and Nuxt UI integration. |
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() from comark/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-component defineComarkComponent({ 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
{
remark: { plugins: { } },
rehype: { options: {...}, plugins: { } },
highlight: { theme: '...', langs: [...] } | false,
toc: { depth: 3, searchDepth: 2 } | false,
}
{
plugins: ComarkPlugin[],
autoUnwrap: true,
autoClose: true,
html: true,
}
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
export default defineNuxtConfig({
modules: ['@nuxtjs/mdc'],
mdc: { highlight: { ... }, remarkPlugins: { ... } },
})
export default defineNuxtConfig({
modules: ['@comark/nuxt'],
})
@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
createParse is sync: no await needed (unlike createMarkdownParser)
- Attribute naming: Comark uses
attrs.lang, not attrs.language
- Frontmatter: stored in
tree.frontmatter, not passed separately
renderMarkdown includes frontmatter: reads tree.frontmatter automatically
- No
unified pipeline: mdc.config.ts hooks (pre, remark, rehype, post) have no equivalent, use ComarkPlugin interface 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, so no changes are 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: { ... } })