File-based content management system for Nuxt.js applications with powerful querying and Vue component rendering in Markdown
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Vue component system for rendering content with MDC (Markdown Components) support, custom component mapping, and prose styling. Enables rich content experiences with Vue components embedded in Markdown.
Primary component for rendering parsed content with full MDC support and customization options.
interface ContentRendererProps {
/** Content object to render (required) */
value: object;
/** Render only excerpt portion of content */
excerpt?: boolean;
/** Root HTML tag for wrapper element */
tag?: string;
/** Custom component mapping for MDC rendering */
components?: Record<string, Component>;
/** Additional data passed to rendered components */
data?: Record<string, unknown>;
/** Use prose components instead of plain HTML tags */
prose?: boolean;
/** CSS classes for root element */
class?: string | Record<string, boolean>;
/** Tags to unwrap from rendered content */
unwrap?: boolean | string;
}
interface ContentRendererSlots {
/** Rendered when content value is empty or null */
empty(): VNode[];
}Usage Examples:
<template>
<!-- Basic content rendering -->
<ContentRenderer :value="article" />
<!-- Render only excerpt -->
<ContentRenderer :value="article" excerpt />
<!-- Custom wrapper tag -->
<ContentRenderer :value="article" tag="article" />
<!-- Custom components for MDC -->
<ContentRenderer
:value="article"
:components="{
'custom-alert': AlertComponent,
'code-block': CodeBlockComponent
}"
/>
<!-- Pass additional data to components -->
<ContentRenderer
:value="article"
:data="{ author: currentUser, theme: 'dark' }"
/>
<!-- Use prose styling -->
<ContentRenderer :value="article" prose class="prose-lg" />
<!-- Handle empty content -->
<ContentRenderer :value="article">
<template #empty>
<p>No content available</p>
</template>
</ContentRenderer>
<!-- Unwrap specific tags -->
<ContentRenderer :value="article" unwrap="p" />
</template>
<script setup>
import AlertComponent from '~/components/Alert.vue';
import CodeBlockComponent from '~/components/CodeBlock.vue';
const { data: article } = await queryCollection('articles')
.path(useRoute().path)
.first();
</script>Component for enabling live content editing and preview functionality.
interface ContentPreviewModeProps {
/** Authentication token for preview mode (required) */
previewToken: string;
/** API endpoint URL for preview data (required) */
api: string;
/** Preview initialization callback (required) */
initializePreview: (preview: PreviewInstance) => void;
}
interface PreviewInstance {
/** Enable preview mode */
enable(): void;
/** Disable preview mode */
disable(): void;
/** Check if preview is active */
isActive(): boolean;
/** Refresh preview data */
refresh(): Promise<void>;
}Usage Examples:
<template>
<div>
<!-- Content with preview mode -->
<ContentRenderer :value="content" />
<!-- Preview mode component -->
<ContentPreviewMode
:preview-token="previewToken"
:api="previewApi"
:initialize-preview="handlePreviewInit"
/>
</div>
</template>
<script setup>
const previewToken = useCookie('preview-token');
const previewApi = '/api/preview';
let previewInstance: PreviewInstance;
const handlePreviewInit = (preview: PreviewInstance) => {
previewInstance = preview;
// Auto-enable preview if token exists
if (previewToken.value) {
preview.enable();
}
};
// Toggle preview mode
const togglePreview = () => {
if (previewInstance.isActive()) {
previewInstance.disable();
} else {
previewInstance.enable();
}
};
</script>// nuxt.config.ts
export default defineNuxtConfig({
content: {
renderer: {
// Global component mapping
components: {
'alert': '~/components/Alert.vue',
'code-sandbox': '~/components/CodeSandbox.vue',
'video-player': '~/components/VideoPlayer.vue'
}
}
}
});<!-- In your .md files -->
# My Article
Regular markdown content here.
::alert{type="warning"}
This is a custom alert component with props!
::
::code-sandbox{id="vue-example"}
::
::video-player{src="/videos/demo.mp4" autoplay}
::<!-- components/Alert.vue -->
<template>
<div :class="alertClasses">
<icon :name="iconName" />
<div class="alert-content">
<slot />
</div>
</div>
</template>
<script setup>
interface Props {
type?: 'info' | 'warning' | 'error' | 'success';
title?: string;
}
const props = withDefaults(defineProps<Props>(), {
type: 'info'
});
const alertClasses = computed(() => [
'alert',
`alert-${props.type}`
]);
const iconName = computed(() => {
const icons = {
info: 'information-circle',
warning: 'exclamation-triangle',
error: 'x-circle',
success: 'check-circle'
};
return icons[props.type];
});
</script>Low-level utilities for working with content AST and tree structures.
/**
* Compresses MDC tree to minimark format for storage/transport
* @param input - MDC AST root node
* @returns Compressed minimark tree
*/
function compressTree(input: MDCRoot): MinimarkTree;
/**
* Decompresses minimark tree back to MDC format
* @param input - Compressed tree data
* @returns Full MDC AST root node
*/
function decompressTree(input: Tree): MDCRoot;
/**
* Tree traversal utility for AST manipulation
* @param tree - Tree to traverse
* @param checker - Function to test if node should be visited
* @param visitor - Function to transform matching nodes
*/
function visit(
tree: Tree,
checker: (node: Node) => boolean,
visitor: (node: Node) => Node | undefined
): void;Usage Examples:
import { compressTree, decompressTree, visit } from '@nuxt/content/runtime';
// Compress content for storage
const compressed = compressTree(content.body);
// Decompress for rendering
const fullTree = decompressTree(compressed);
// Transform nodes in tree
visit(tree,
(node) => node.type === 'element' && node.tag === 'img',
(node) => {
// Add lazy loading to images
if (node.props) {
node.props.loading = 'lazy';
}
return node;
}
);interface MDCRoot {
type: 'root';
children: MDCNode[];
}
interface MDCNode {
type: string;
tag?: string;
props?: Record<string, unknown>;
children?: MDCNode[];
value?: string;
}
interface MinimarkTree {
type: 'root';
children: MinimarkNode[];
}
interface MinimarkNode {
type: string;
tag?: string;
props?: Record<string, unknown>;
children?: MinimarkNode[];
value?: string;
}
type Tree = MDCRoot | MinimarkTree;
type Node = MDCNode | MinimarkNode;
interface Component {
name: string;
props?: Record<string, unknown>;
slots?: Record<string, () => VNode[]>;
}Install with Tessl CLI
npx tessl i tessl/npm-nuxt--content