Skip to content

Rendering articles (ArticleContent)

A published article’s body is plain Markdown (article.markdown). ArticleContent renders it to sanitized HTML — GFM on, HTML sanitized — the same contract as @ghostwritr/react’s <ArticleContent>. It’s a render-function component, so it works anywhere Vue 3 runs: Nuxt, Vite SSR, and the browser (SPA).

The renderer is a separate, opt-in entry — it keeps the Markdown dependencies out of data-only consumers:

import { ArticleContent } from "@ghostwritr/vue/article-content";

Pass the article’s markdown. With no class it renders a bare <div> wrapper; pass class to apply prose/Tailwind:

pages/blog/[slug].vue
<script setup lang="ts">
import { fetchArticle } from "@ghostwritr/vue";
import { ArticleContent } from "@ghostwritr/vue/article-content";
const route = useRoute();
const { data: article } = await useAsyncData(`gw-${route.params.slug}`, () =>
fetchArticle({
siteId: useRuntimeConfig().public.ghostwritrSiteId,
slug: String(route.params.slug),
}),
);
if (!article.value) throw createError({ statusCode: 404, statusMessage: "Not found" });
</script>
<template>
<article v-if="article">
<h1>{{ article.title }}</h1>
<ArticleContent :markdown="article.markdown" class="prose" />
</article>
</template>
PropTypeDefaultEffect
markdownstring— (required)The article’s Markdown body.
classstringundefinedWrapper class on the <div> — apply prose/Tailwind here.
remarkPluginsPluggableListundefinedExtra remark plugins, appended after the built-in remark-gfm.
rehypePluginsPluggableListundefinedExtra rehype plugins, appended after the built-in rehype-sanitize.

The built-ins always apply; your plugins run after them in the same pipeline.

<ArticleContent
:markdown="article.markdown"
class="prose"
:remark-plugins="[/* appended after remark-gfm */]"
:rehype-plugins="[/* appended after rehype-sanitize */]"
/>

ArticleContent is a thin wrapper over renderMarkdown — the underlying synchronous Markdown → sanitized-HTML function. Reach for it directly when you need the HTML string itself (an RSS body, an email, a v-html binding you control):

import { renderMarkdown } from "@ghostwritr/vue/article-content";
const html = renderMarkdown(article.markdown, {
remarkPlugins: [/* appended after remark-gfm */],
rehypePlugins: [/* appended after rehype-sanitize */],
});

renderMarkdown(markdown, opts?) returns a string. It applies the same GFM + sanitize built-ins, and appends your remarkPlugins/rehypePlugins after them — identical to the component. See The article shape for what article.markdown contains.