Render Markdown with Components from a CMS or Database
Parse the Markdown on the server when an editor saves it, and store the resulting MarkdownDocument as JSON. Any client then renders that JSON with <MarkdownDocument>, with no parser in the browser and no build step.
Content from a CMS or a database changes after you deploy. A compiler such as MDX turns content into code at build time, so new content needs a new build. Comark parses at runtime into plain data. See Comark vs MDX for a full comparison.
Parse and store the document
A MarkdownDocument is a JSON-safe object: { nodes, frontmatter, meta }. Parse once when the content changes, then store or cache the result. Sanitize before you store, so unsafe content never reaches the database:
import { createMarkdownParser } from 'comark'
import security from 'comark/plugins/security'
const parse = createMarkdownParser({
plugins: [security({ blockedTags: ['script', 'iframe', 'embed', 'form', 'base', 'meta', 'link', 'style', 'object'] })],
})
export async function savePage(slug: string, markdown: string) {
const document = await parse(markdown)
// `db` stands for your database client
await db.pages.upsert({ slug, markdown, document: JSON.stringify(document) })
}This example keeps the source Markdown for the editor and the document for rendering. Your API then returns the stored document.
Render the document on any client
<MarkdownDocument> renders a parsed document without a parse step. Map component tags to your own components with the components prop:
<script setup lang="ts">
import { MarkdownDocument } from '@comark/vue'
import Alert from './components/Alert.vue'
const document = await fetch('/api/pages/pricing').then(res => res.json())
</script>
<template>
<MarkdownDocument :value="document" :components="{ alert: Alert }" />
</template>import { MarkdownDocument } from '@comark/react'
import Alert from './components/Alert'
export default async function PricingPage() {
const document = await fetch('https://cms.example.com/api/pages/pricing').then(res => res.json())
return <MarkdownDocument value={document} components={{ alert: Alert }} />
}import { renderHtmlFromDocument } from '@comark/html'
const document = await fetch('https://cms.example.com/api/pages/pricing').then(res => res.json())
const html = await renderHtmlFromDocument(document)Svelte and Angular use the same pattern with <MarkdownDocument> and <comark-markdown-document>. The document format doesn't depend on a framework, so one stored document serves every client.
Pass runtime data
Content often refers to values that change per request, for example the user name or a price. Pass them in the data prop, and reference them with a : prop in the Markdown:
Welcome back, :badge{:label="data.user.name"}!<MarkdownDocument :value="document" :data="{ user: { name: 'Ada' } }" />To insert a value directly into text, add the binding plugin. It adds the {{ data.user.name || guest }} syntax. See data binding for the full scope.
Turn the document back into Markdown
renderMarkdown() converts a document into a Markdown string, with frontmatter and component syntax. Use it when you edit the document in code, or when you keep only the document:
import { renderMarkdown } from 'comark/render'
// `row` is the stored page from your database
const document = JSON.parse(row.document)
document.frontmatter.title = 'New pricing'
const markdown = await renderMarkdown(document)
await savePage(row.slug, markdown)The output can differ from the original formatting. For example, a component with more than three attributes moves them to a YAML block. See the maxInlineAttributes option.
FAQ
<MarkdownDocument> only renders. The <Markdown> component imports the parser, so use it only when you parse in the browser.allowedTags or blockedTags options of the security plugin.Next steps
Docs and blogs
Parse Markdown files with frontmatter, a table of contents, and highlighted code, then render them with your own components in Nuxt, Next.js, Astro, or SvelteKit.
CLIs and agents
Print Markdown as styled ANSI output in Node.js CLIs and coding agents, with highlighted code, tables, and alerts, using @comark/ansi.