MDX: Menggabungkan Markdown dengan React Components

Panduan lengkap menggunakan MDX untuk membuat content yang interaktif dalam Next.js

Khairil Rahman
10 min read

MDX: Menggabungkan Markdown dengan React Components

MDX (Markdown for the component era) memungkinkan kita untuk menggunakan React components dalam markdown content. Sangat powerful untuk membuat interactive content!

What is MDX?

MDX adalah superset dari markdown yang memungkinkan kita untuk:

  • Menggunakan React components dalam markdown
  • Menulis JSX expression dalam content
  • Membuat interactive content yang rich
  • Maintain content yang mudah dibaca

Basic MDX Syntax

Mixing Markdown dan JSX

jsx
# Blog Post Title Ini adalah content markdown biasa. <Callout type="info"> Ini adalah React component yang akan dirender! </Callout> Content markdown lainnya dengan **bold** dan *italic* text.

Dynamic Content

jsx
const currentDate = new Date().toLocaleDateString('id-ID'); --- # Today's Date Today is: {currentDate} Dan ini adalah <Badge variant="outline">React component</Badge> yang menggunakan data dinamis.

MDX dengan Next.js

Setup MDX in Next.js

  1. Install dependencies:
bash
npm install @next/mdx gray-matter remark remark-gfm
  1. Update next.config.mjs:
javascript
import { withMDX } from "@next/mdx"; const nextConfig = { pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], }; export default withMDX()(nextConfig);
  1. Create MDX content:
mdx
--- title: "My First MDX Post" date: "2025-11-05" --- # Welcome to MDX This is a {new Date().toLocaleDateString()} blog post. <Counter />

Advanced MDX Features

Custom Components

jsx
// components/ui/CodeBlock.tsx export function CodeBlock({ children, filename, language }) { return ( <div className="code-block"> {filename && <div className="code-header">{filename}</div>} <pre className={`language-${language}`}> <code>{children}</code> </pre> </div> ); } // Usage dalam MDX <CodeBlock filename="example.ts" language="typescript"> {` import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); } `} </CodeBlock>;

Interactive Charts

jsx
// components/Chart.tsx import { Line } from "react-chartjs-2"; export function BlogChart({ data }) { return ( <div className="my-8"> <Line data={data} options={{ responsive: true, plugins: { legend: { position: "top" }, }, }} /> </div> ); } // Usage <BlogChart data={{ labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], datasets: [ { label: "Performance", data: [12, 19, 3, 5, 2, 3], borderColor: "rgb(75, 192, 192)", tension: 0.1, }, ], }} />;

Interactive Code Playground

jsx
// components/CodePlayground.tsx import { useState } from "react"; export function CodePlayground({ initialCode }) { const [code, setCode] = useState(initialCode); const [output, setOutput] = useState(""); const runCode = () => { // Simple code execution (in real app, use sandbox) try { setOutput(eval(code)); } catch (error) { setOutput("Error: " + error.message); } }; return ( <div className="code-playground"> <div className="code-editor"> <textarea value={code} onChange={(e) => setCode(e.target.value)} className="w-full h-32 p-2 border rounded" /> <button onClick={runCode} className="mt-2 px-4 py-2 bg-blue-500 text-white rounded" > Run Code </button> </div> {output && ( <div className="code-output"> <h4>Output:</h4> <pre>{output}</pre> </div> )} </div> ); }

MDX dengan Content Management

Frontmatter Processing

yaml
--- title: "Interactive Blog Post" description: "Learn MDX with interactive components" date: "2025-11-05" tags: ["mdx", "interactive", "nextjs"] components: - Callout - CodeBlock - Chart --- # Blog Content Here

Dynamic Content Loading

jsx
// lib/content/loaders.ts import fs from "fs"; import path from "path"; import matter from "gray-matter"; import { serialize } from "next-mdx-remote/serialize"; export async function loadMdxContent(slug) { const filePath = path.join(process.cwd(), "content", `${slug}.mdx`); const fileContents = fs.readFileSync(filePath, "utf8"); const { data, content } = matter(fileContents); const mdxSource = await serialize(content, { parseFrontmatter: true, mdxOptions: { remarkPlugins: [require("remark-gfm")], rehypePlugins: [require("rehype-prism-plus")], }, }); return { mdxSource, frontmatter: data, }; }

Best Practices

1. Component Organization

components/
├── ui/
│   ├── Callout.tsx
│   ├── Badge.tsx
│   └── CodeBlock.tsx
├── blog/
│   ├── CodePlayground.tsx
│   ├── BlogChart.tsx
│   └── InteractiveDemo.tsx
└── layout/
    ├── ArticleLayout.tsx
    └── BlogLayout.tsx

2. Styling Strategy

  • Use Tailwind classes untuk styling
  • Maintain consistent design system
  • Support dark/light mode
  • Responsive design

3. Performance

jsx
// Lazy load heavy components import dynamic from "next/dynamic"; const BlogChart = dynamic(() => import("./BlogChart"), { loading: () => <div>Loading chart...</div>, ssr: false, }); // Usage <BlogChart data={chartData} />;

Kesimpulan

MDX adalah powerful tool untuk:

  • Membuat content yang interactive
  • Menulis dokumentasi yang rich
  • Building developer blogs yang engaging
  • Menggabungkan static content dengan dynamic components

Dengan MDX, kita bisa membuat content yang tidak hanya informatif, tetapi juga interaktif dan engaging untuk readers!

Related Articles

Tutorial lengkap untuk memulai development dengan Next.js 15 dan fitur-fitur baru App Router

Membahas dasar-dasar penggunaan Next.js 15 dengan App Router, dari Server Components hingga Streaming.

Enjoyed this article?

Follow me for more insights on web development and modern frontend technologies.