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
jsxconst 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
- Install dependencies:
bashnpm install @next/mdx gray-matter remark remark-gfm
- Update
next.config.mjs:
javascriptimport { withMDX } from "@next/mdx"; const nextConfig = { pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], }; export default withMDX()(nextConfig);
- 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!