@davaux/markdown
Adds support for .page.md route files. Markdown is parsed with marked and rendered through the normal Davaux layout chain. Frontmatter is parsed and automatically applied to ctx.head, making it easy to manage page titles and descriptions from your Markdown files.
Installation
npm install @davaux/markdown
Setup
Register the plugin in davaux.config.ts:
// davaux.config.ts
import { defineConfig } from 'davaux/config'
import { markdown } from '@davaux/markdown'
export default defineConfig({
plugins: [markdown()],
})
Creating Markdown pages
Once the plugin is registered, any file matching *.page.md inside src/routes/ becomes a route:
src/routes/
├── blog/
│ ├── hello-world.page.md → GET /blog/hello-world
│ └── second-post.page.md → GET /blog/second-post
└── about.page.md → GET /about
Frontmatter
YAML frontmatter at the top of the file is parsed automatically. The title and description keys are applied to ctx.head:
---
title: Hello World
description: My first blog post using Davaux Markdown.
---
# Hello World
Welcome to my first post!
This is equivalent to calling ctx.head.title = 'Hello World' in a .page.tsx handler.
Accessing frontmatter in layouts
The full frontmatter object is available as ctx.state.frontmatter when processed by this plugin:
// In _layout.tsx
const fm = ctx.state.frontmatter as Record<string, string> | undefined
const publishedAt = fm?.date // e.g. "2026-05-15"
Exporting frontmatter
You can also export the frontmatter as a typed value for use in listing pages:
---
title: Hello World
date: 2026-05-15
tags: [web, node]
---
Content here.
// In a listing page — access via dynamic import or a build-time scan
import { frontmatter } from './blog/hello-world.page.md'
console.log(frontmatter.title) // "Hello World"
console.log(frontmatter.date) // "2026-05-15"
Styling the article
Markdown pages are wrapped in <article class="prose"> by default. Use the articleClass option to customize the class name, or pass an empty string to omit the wrapper entirely:
markdown({
articleClass: 'prose-lg', // matches your CSS
})
.prose h1 { font-size: 2rem; }
.prose h2 { font-size: 1.5rem; }
.prose code { background: #f5f5f5; padding: 0.2em 0.4em; }
Syntax highlighting
The plugin does not include syntax highlighting by default — pass marked extensions via markedExtensions:
import { markdown } from '@davaux/markdown'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
export default defineConfig({
plugins: [
markdown({
markedExtensions: [
markedHighlight({
highlight: (code, lang) =>
hljs.highlight(code, { language: lang || 'plaintext' }).value,
}),
],
}),
],
})
Runtime rendering — renderMarkdown()
For content stored in a database or CMS, use renderMarkdown() to convert a markdown string to HTML at request time. It uses the same marked pipeline as .page.md route files, so the output is consistent:
// src/routes/blog/[slug].page.tsx
import { definePage } from 'davaux'
import { renderMarkdown } from '@davaux/markdown'
import { db } from '~/lib/db.ts'
export default definePage(async (ctx) => {
const post = await db.posts.findBySlug(ctx.params.slug)
if (!post) {
ctx.res.statusCode = 404
return <p>Not found</p>
}
ctx.head.title = post.title
const html = await renderMarkdown(post.body)
return (
<article class="prose" innerHTML={html} />
)
})
renderMarkdown returns a plain HTML string with no wrapper by default. Pass articleClass to add the same <article> container the plugin uses:
const html = await renderMarkdown(post.body, { articleClass: 'prose' })
// → <article class="prose">...rendered HTML...</article>
Pass the same markedExtensions you use in davaux.config.ts to keep syntax highlighting consistent between route files and runtime-rendered content:
import { renderMarkdown } from '@davaux/markdown'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
const highlight = markedHighlight({
highlight: (code, lang) =>
hljs.highlight(code, { language: lang || 'plaintext' }).value,
})
const html = await renderMarkdown(post.body, { markedExtensions: [highlight] })
Migrating to MDX
If you need to embed JSX components in your Markdown, migrate to .page.mdx files and switch to the @davaux/mdx plugin. Both plugins can coexist in the same project — Markdown handles .page.md and MDX handles .page.mdx. Frontmatter behavior is identical between them.