Glass Box Terrarium, explained from the inside

Last updated · Latest source on GitHub ↗

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

Looking inside

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:

app/components/content/ProsePre.vue ↗1–18as of
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:

layers/journal/app/pages/t/journal/[space]/[...slug].vue ↗66–67as of
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.vue in any layer's components/content/ changes links in every Tenant. For one Tenant, pass components to its ContentRenderer.
  • An override is a copy. If a release of @nuxtjs/mdc changes 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 MiddenGloss through components by 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 ↗

Something went wrong

This content couldn’t be loaded. Reloading the page usually fixes it.

Technical details
route: /t/glassbox/drafts/nuxt-content/prose-overrides