๐ Next.js์์ MDX ์ฌ์ฉ ๊ฐ์ด๋
๐ Next.js์์ MDX ์ฌ์ฉ ๊ฐ์ด๋
MDX๋ Markdown๊ณผ JSX๋ฅผ ๊ฒฐํฉํ์ฌ ์ ์ ์ธ ์ฝํ
์ธ ์ ๋์ ์ธ ์ปดํฌ๋ํธ๋ฅผ ํจ๊ป ์ฌ์ฉํ ์ ์๋ ๊ฐ๋ ฅํ ํฌ๋งท์
๋๋ค.
Next.js App Router์์๋ ์ฝ๊ฒ MDX๋ฅผ ํตํฉํ๊ณ ์ฌ์ฉํ ์ ์๋๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
โ 1. MDX๋?
MDX(Markdown + JSX)๋ ์ผ๋ฐ์ ์ธ Markdown ํ์ผ ์์ React ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ฝ์
ํ ์ ์๋ ๋ฌธ์ ํฌ๋งท์
๋๋ค.
์ด๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ ์์
์ด ๊ฐ๋ฅํฉ๋๋ค:
- ๋ธ๋ก๊ทธ์์ <VideoPlayer />, <CodeBlock /> ์ฝ์
- ๊ธฐ์ ๋ฌธ์์ <Tabs />, <Alert />์ ๊ฐ์ ์ธํฐ๋ํฐ๋ธ ์ปดํฌ๋ํธ ์ฌ์ฉ
- Markdown ๊ธฐ๋ฐ ํฌํธํด๋ฆฌ์ค ํ์ด์ง ๊ตฌ์ถ
โ 2. ํจํค์ง ์ค์น
๋ค์ ๋ช ๋ น์ด๋ฅผ ํตํด MDX ํตํฉ์ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค:
npm install @next/mdx @mdx-js/loader @mdx-js/react
๋๋
yarn add @next/mdx @mdx-js/loader @mdx-js/react
โ 3. next.config.js ์ค์
MDX ํ์ผ์ ํ์ด์ง๋ก ์ธ์ํ๊ธฐ ์ํด next.config.js์ ์๋ ์ค์ ์ ์ถ๊ฐํฉ๋๋ค.
// next.config.js
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
});
module.exports = withMDX({
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
});
.mdx ํ์ฅ์๋ฅผ ํ์ด์ง๋ก ์ธ์ํ๋ ค๋ฉด pageExtensions๋ฅผ ๋ฐ๋์ ์ค์ ํด์ผ ํฉ๋๋ค.
โ 4. MDX ํ์ผ ์์ฑ ๋ฐ ์ฌ์ฉ
# ์๊ฐํฉ๋๋ค
Next.js์์๋ **MDX**๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์์ฒ๋ผ ์ปดํฌ๋ํธ๋ฅผ ์ฝ์
ํ ์ ์์ต๋๋ค.
<CustomButton>ํด๋ฆญํ์ธ์!</CustomButton>
ํด๋น ํ์ผ์ app/docs/page.mdx์ ์์น์์ผ ํ์ด์ง์ฒ๋ผ ์ฌ์ฉํ๊ฑฐ๋, ๋์ ์ผ๋ก importํ ์ ์์ต๋๋ค.
โ 5. ์ปค์คํ ์ปดํฌ๋ํธ ์ฐ๊ฒฐ
MDX ๋ด๋ถ์์ ์ฌ์ฉํ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
// mdx-components.tsx
import CustomButton from './components/CustomButton';
export const components = {
CustomButton,
};
// app/docs/page.tsx
'use client';
import { MDXProvider } from '@mdx-js/react';
import Content from './page.mdx';
import { components } from '../../mdx-components';
export default function DocsPage() {
return (
<MDXProvider components={components}>
<Content />
</MDXProvider>
);
}
โ 6. next-mdx-remote๋ฅผ ํตํ ๋์ ๋ก๋ฉ
์ธ๋ถ CMS๋ ํ์ผ์์คํ ์์ MDX ์ฝํ ์ธ ๋ฅผ ๋ถ๋ฌ์ค๋ ค๋ฉด next-mdx-remote๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ค์น
npm install next-mdx-remote
์์
// app/blog/[slug]/page.tsx
import fs from 'fs';
import path from 'path';
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';
import { components } from '../../../mdx-components';
export async function generateStaticParams() {
return [{ slug: 'hello' }];
}
export default async function BlogPage({ params }: { params: { slug: string } }) {
const file = fs.readFileSync(path.join('content', `${params.slug}.mdx`), 'utf-8');
const mdxSource = await serialize(file);
return <MDXRemote {...mdxSource} components={components} />;
}
โ 7. ์ฝ๋ ํ์ด๋ผ์ดํ ์ถ๊ฐ (์ต์ )
์ฝ๋ ๋ธ๋ก ํ์ด๋ผ์ดํ ์ ์ํ๋ค๋ฉด rehype-highlight ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ค์น
npm install rehype-highlight
์ค์
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
rehypePlugins: [require('rehype-highlight')],
},
});
โ 8. App Router์์ MDX ์ฌ์ฉ ํ
ํญ๋ชฉ ์ค๋ช
App Router ์ง์ ์ฌ๋ถ | โ .mdx ํ์ด์ง ์ง์ ์ฌ์ฉ ๊ฐ๋ฅ |
ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ | <Content />๋ ๋ฐ๋์ 'use client' ์๋จ ์ ์ธ |
๋ ์ด์์ ๊ณต์ | .mdx๋ ์ผ๋ฐ tsx์ ๋์ผํ๊ฒ layout.tsx์ ์ฐ๋๋จ |
์ปดํฌ๋ํธ ๋งํฌ์ ์ ํ | React ์ ์ฉ ๊ธฐ๋ฅ (useState, useEffect)์ .tsx์์๋ง |
โ ์์ฝ
๊ธฐ๋ฅ ๋ฐฉ๋ฒ
MDX ์ฌ์ฉ | @next/mdx ์ค์ ๋ฐ .mdx ํ์ผ ์์ฑ |
์ปดํฌ๋ํธ ์ฐ๊ฒฐ | @mdx-js/react์ MDXProvider ์ฌ์ฉ |
์ฝ๋ ํ์ด๋ผ์ดํ | rehype-highlight ํ๋ฌ๊ทธ์ธ ์ค์น ๋ฐ ์ ์ฉ |
์ธ๋ถ ์ฝํ ์ธ ๋ ๋๋ง | next-mdx-remote ํ์ฉ (serialize, MDXRemote) |
App Router ํธํ | .mdx ํ์ด์ง ์ง์ ๋ผ์ฐํ ๊ฐ๋ฅ |
Next.js, MDX, Markdown, JSX, ์ ์ ํ์ด์ง, ๋ธ๋ก๊ทธ, ๊ธฐ์ ๋ฌธ์, ์ปดํฌ๋ํธ ์ฝ์ , next-mdx-remote, rehype-highlight, App Router, SEO ์ต์ ํ