MDX and Markdown

Learn how to use MDX and Markdown for content routes in Bini.js.

What is MDX?

MDX is an extension to Markdown that allows you to write JSX components directly in your Markdown files. Bini.js supports .mdx and .md files as content routes out of the box.

@mdx-js/rollup is bundled internally, so no separate installation or Vite configuration is required. This makes it easy to create rich, interactive content pages.

MDX Pages

Create an MDX page by adding a .mdx file anywhere in src/app/. The file is compiled to a React component and rendered as a page.

src/app/
├── about.mdx              → /about
├── blog/
│   ├── page.mdx           → /blog
│   └── [slug].mdx         → /blog/:slug
└── contact.mdx            → /contact
app/about.mdx
---
export const metadata = {
  title: 'About Us',
  description: 'Learn more about our company',
}
---

# About Us

Welcome to our company! This is a regular **Markdown** page with JSX support.

<Button variant="primary">Get Started</Button>

## Our Mission

We build amazing products with Bini.js.

Markdown Pages

Bini.js also supports plain .md files. They go through the same MDX pipeline, which means they also support JSX and imports.

src/app/
├── docs/
│   └── getting-started.md  → /docs/getting-started
├── privacy.md              → /privacy
└── terms.md                → /terms
app/terms.md
# Terms of Service

## 1. Acceptance of Terms

By using our service, you agree to these terms.

## 2. User Responsibilities

Users are responsible for their content and activity.

## 3. Termination

We reserve the right to terminate accounts that violate these terms.

---

*Last updated: January 2024*

Both .mdx and .md are compiled through the same MDX pipeline with full JSX, import, and export support. There is no plain-markdown-only mode.

Metadata in MDX

Export metadata from any MDX page to set page titles, descriptions, and Open Graph tags.

app/blog/post.mdx
---
export const metadata = {
  title: 'Blog Post',
  description: 'A comprehensive guide to Bini.js',
  openGraph: {
    title: 'Blog Post',
    description: 'A comprehensive guide to Bini.js',
    images: ['/og-image.png'],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Blog Post',
    creator: '@bini_js',
  },
}
---

# Blog Post

This is a blog post written in MDX with full metadata support.

Root layout metadata is injected into index.html at build time. Nested layout titles update document.title at runtime.

Imports in MDX

You can import components, utilities, and other files directly in MDX:

app/interactive.mdx
import { Button } from '@/components/Button'
import { BlogLayout } from '@/components/BlogLayout'
import { useTheme } from '@/hooks/useTheme'

export const metadata = {
  title: 'Interactive Page',
}

# Interactive Page

<BlogLayout>
  <p>This page uses imported components!</p>
  <Button variant="primary">Click Me</Button>
</BlogLayout>

Auto-imports (useState, Link, getEnv, etc.) apply to MDX files the same as any other page.

Extension Priority

When multiple files share the same base name in a folder, Bini.js uses this priority order:

.tsx > .jsx > .ts > .js > .mdx > .md

For example, if both page.tsx and page.mdx exist in the same folder:

  • page.tsx will be used (higher priority)
  • page.mdx is ignored
src/app/
├── about/
│   ├── page.tsx          ← Used (higher priority)
│   └── page.mdx          ← Ignored
├── blog/
│   ├── page.mdx          ← Used (higher priority than .md)
│   └── page.md           ← Ignored
└── contact.md            → /contact

Styling MDX Content

CSS Modules, plain CSS imports, and Tailwind utility classes work directly in MDX files:

app/about.mdx
import styles from './About.module.css'
import { Button } from '@/components/Button'

# About Us

<div className={styles.container}>
  <p className="text-slate-600 dark:text-slate-300">
    This uses Tailwind classes and CSS Modules!
  </p>
  <Button>Learn More</Button>
</div>

Note: Tailwind's Preflight reset strips default styling from headings and bold text. Wrap plain-markdown regions in a prose class from @tailwindcss/typography if you want them to look styled by default.

MDX Configuration

You can pass options directly to the bundled @mdx-js/rollup plugin via the biniroute() configuration:

vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniroute } from 'bini-router'

export default defineConfig({
  plugins: [
    react(),
    ...biniroute({
      mdx: {
        remarkPlugins: [/* add remark plugins here */],
        rehypePlugins: [/* add rehype plugins here */],
      },
    }),
  ],
})

This is useful for adding syntax highlighting, custom markdown transformations, or other content processing.

Complete Example

Here is a comprehensive example showing MDX and Markdown usage:

src/app/
├── layout.tsx                 ← Root layout
├── page.tsx                   → /
├── about.mdx                  → /about
├── blog/
│   ├── layout.tsx             ← Blog layout
│   ├── page.mdx               → /blog
│   ├── loading.tsx            ← Blog loading UI
│   ├── [slug].mdx             → /blog/:slug
│   └── _components/           ← Private folder
│       └── PostCard.tsx
├── docs/
│   ├── [[...slug]]/
│   │   └── page.md            → /docs (optional catch-all)
│   │                           → /docs/getting-started
│   └── _components/
│       └── Sidebar.tsx
└── contact.mdx                → /contact

# Example MDX with Imports and Metadata

// app/about.mdx
---
export const metadata = {
  title: 'About',
  description: 'Learn about our company',
}
---

import { TeamMember } from '@/components/TeamMember'
import { useTheme } from '@/hooks/useTheme'

# About Our Company

We build amazing things with Bini.js.

<div className="grid grid-cols-2 gap-4">
  <TeamMember name="John" role="Developer" />
  <TeamMember name="Jane" role="Designer" />
</div>

## Our Values

- **Quality** — We ship polished code
- **Speed** — We move fast
- **Community** — We support our users