Draft: this page sits outside the guide's focus.
Nuxt ContentLesson
Overriding prose components
Every heading, link and code block in a Markdown page comes out of a Vue component. Replace one of them and every page that renders Markdown changes at once. That reach is useful for a change every page should get, and a trap when only one site of several should get it.
Know prose components already? Skip to In Terrarium.
Outside the box · General
The concept
When Nuxt Content renders a Markdown body, each HTML element the Markdown produces is drawn by a prose component: a paragraph by ProseP, a link by ProseA, a fenced code block by ProsePre. The stock ones come from @nuxtjs/mdc, which Nuxt Content builds on.
To replace one, create a component with the same name in your project's components/content/ folder, such as components/content/ProseA.vue. The docs advise starting from the original's source and keeping exactly its props. ProsePre, for example, receives the block's code, language, filename, highlights and meta.
A replacement applies wherever Markdown renders, because components are registered by name for the whole app. For a change on one page only, <ContentRenderer> takes a components prop: a map from an element, such as table, to the component that draws it in that one render.
Full referenceNuxt Content docs: Prose Components
In Terrarium
Terrarium replaces one prose component. app/components/content/ProsePre.vue sends a mermaid code block to the diagram component and draws every other block as the stock one would:
1<template>
2 <MermaidDiagram v-if="props.language === 'mermaid'" :code="props.code" />
3 <pre v-else :class="props.class"><slot /></pre>
4</template>
⋯// …
11const props = withDefaults(defineProps<{
12 code?: string
13 language?: string | null
14 filename?: string | null
15 highlights?: unknown[]
16 meta?: string | null
17 class?: string | null
18}>(), { code: '', language: null, filename: null, highlights: () => [], meta: null, class: null })
It declares all of the stock component's props and keeps its one style rule, so other code blocks render unchanged.
It sits in the Platform's own app/, not in a Tenant's layer.
When one wants a different element, it passes components to its own ContentRenderer. Journal pages wrap every table in a scrolling box that way:
66 <article v-if="page" class="jd-prose">
67 <ContentRenderer :value="page" :components="{ table: JournalScrollTable }" />
Most Tenants need neither. They restyle the rendered elements with CSS scoped under their wrapper class, as the Blog's layers/blog/app/assets/theme.css does for paragraphs, headings and links.
Why it's built this way
Why a prose override lives at the Platform root: docs/agents/tenant-layers.md.
What to watch for
- An override in a layer is still app-wide. A
ProseA.vuein any layer'scomponents/content/changes links in every Tenant. For one Tenant, passcomponentsto itsContentRenderer. - An override is a copy. If a release of
@nuxtjs/mdcchanges its stock component, the replacement doesn't follow. Compare the two when you upgrade. - A component the Markdown never names isn't bundled for it. Nuxt Content's build lists only the components the content names. The Midden's dig report adds gloss elements to the body in code, so it passes
MiddenGlossthroughcomponentsby hand.
Try it in your project
Make external links open in a new tab on every page. Copy the stock ProseA into components/content/ProseA.vue, keep its href and target props, and set target to _blank when href starts with http:
<!-- components/content/ProseA.vue -->
<script setup lang="ts">
const props = defineProps<{ href?: string, target?: string }>()
const external = computed(() => props.href?.startsWith('http'))
</script>
<template>
<NuxtLink :href="props.href" :target="external ? '_blank' : props.target"><slot /></NuxtLink>
</template>
Then undo it and get the same effect on one page only, by passing the component as :components="{ a: ExternalLink }" to that page's ContentRenderer.
Where next
Last updated · Latest source on GitHub ↗