Nuxt

Set up Comark Content in a Nuxt app.

Comark Content fits Nuxt's server/client split naturally: the Content instance lives in server/, a single catch-all route exposes it under /api/content, and pages read it through the browser client with useAsyncData.

Install the packages

pnpm add comark-content @comark/vue

Create the Content instance

The instance lives on the server, next to your Nitro routes:

server/utils/content.ts
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'),
})

Mount the handler

A catch-all API route forwards the h3 event to content.handler() as a web-standard Request:

server/api/content/[...path].ts
// defineEventHandler, content and toWebRequest are auto-imported

export default defineEventHandler((event) => content.handler(toWebRequest(event)))

Create the client

Expose a shared createContentClient() as a composable. Passing Nuxt's $fetch means data fetched during SSR is hydrated on the client without a refetch:

app/composables/client-content.ts
import { createContentClient } from 'comark-content/client'

export const clientContent = createContentClient({
  // Nuxt's $fetch so during SSR we have direct function calling
  // Saving additional API call
  fetch: $fetch,
})

Render a page

Fetch the entry with useAsyncData and hand the page to <MarkdownDocument> from @comark/vue:

app/pages/index.vue
<script setup lang="ts">
import { MarkdownDocument } from '@comark/vue'

const { data: page } = await useAsyncData('home', () => clientContent.get('/'))

if (!page.value) {
  throw createError({ statusCode: 404, message: 'content not found' })
}

useSeoMeta({
  title: () => page.value!.data.title,
  description: () => page.value!.data.description,
})
</script>

<template>
  <MarkdownDocument :value="page" />
</template>

Map Markdown components to your own (Nuxt UI, custom components) through the components prop, read more in Comark's Vue renderer.

Generate source types

Type clientContent.get() from your frontmatter by generating source types, then commit-ignore the output:

Terminal
npx comark-content prepare
The command will scan multiple directories to find the content.ts file, including the ./server/utils/ directory and generate a comark-content.d.ts file.

Add comark-content.d.ts to your tsconfig.json include, see Type Safety.

Prerender content pages

If you want to pre-render the content pages when running nuxt build without having to use the nitro.prerender.crawlLinks option, you can import your content instance and use the prerender:routes hook:

nuxt.config.ts
import { content } from './server/utils/content'

export default defineNuxtConfig({
  hooks: {
    'prerender:routes': async (ctx) => {
      const pages = await content.list()
      for (const page of pages) {
        ctx.routes.add(page.path)
      }
    }
  },
})

Going further

Nuxt example

The full runnable app: YAML, JSON, Markdown, and json-render pages with Nuxt UI.

Nuxt x Better Auth

A private, auth-gated content area on top of the same wiring.

Client & handler

How the handler/client pair works, and how to extend it with plugin pairs.

Caching

Persist parsed bodies with any unstorage driver to speed up reads.