Quickstart
Go from an empty Nuxt 4 app to Markdown rendering at its own URL in about five minutes.
Start with an empty Nuxt 4 app. In about five minutes, you will render content/index.md at / and a second Markdown file at /guide.
Install
Add Ginko with the Nuxt CLI:
npx nuxi module add @lupinum/ginko-contentOr install and register it yourself:
pnpm add @lupinum/ginko-content zodexport default defineNuxtConfig({
modules: ['@lupinum/ginko-content']
})The current package supports Nuxt 4.5.1 through Nuxt 4.x, Vue 3.5.35 through Vue 3.x, and Node.js 22.18–22.x, 24.11–24.x, or 26+.
Declare a collection
Create content.config.ts at the app root. Export the pages handle so components can import the collection instead of referring to it by a string name.
import { defineCollection, defineContentConfig } from '@lupinum/ginko-content/config'
import { z } from 'zod'
export const pages = defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
title: z.string(),
description: z.string().optional()
})
})
export default defineContentConfig({
collections: { pages }
})Add two Markdown files
---
title: Welcome
description: The home page for this content site.
---
# Welcome
This page comes from `content/index.md`.
[Read the guide](/guide)---
title: Guide
description: A second route backed by Markdown.
---
# Guide
Both pages belong to the same typed collection.index.md resolves to / and guide.md resolves to /guide.
Render them at their URL
Use one catch-all route for the collection. useContentPage resolves the current URL; the page component owns the 404, metadata, and rendering policy.
<script setup lang="ts">
import { createError, useSeoMeta } from '#imports'
import { pages } from '~~/content.config'
definePageMeta({ key: route => route.path })
const { page } = await useContentPage(pages)
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page not found',
fatal: true
})
}
useSeoMeta({
title: () => page.value?.title,
description: () => page.value?.description
})
</script>
<template>
<main v-if="page">
<ContentRenderer :value="page" />
</main>
</template>Pass the whole document to<ContentRenderer>, notpage.body. The renderer uses the document's collection, locale, and resolved references while rendering Markdown. Passing only the body drops that context.
Run it
pnpm devOpen http://localhost:3000/ and /guide to see rendered Markdown, and /missing to see your 404.
Add another Markdown file matched by the pages collection and Ginko exposes it at the corresponding route.