Markdown and MDX
Markdown (opens in a new tab)은 텍스트를 포맷하는 데 사용되는 경량 마크업 언어입니다. 이를 통해 일반 텍스트 구문을 사용하여 작성하고 구조적으로 유효한 HTML로 변환할 수 있습니다. 웹사이트 및 블로그의 콘텐츠 작성에 자주 사용됩니다.
다음과 같이 작성합니다...
I **love** using [Next.js](https://nextjs.org/)
출력:
<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>
MDX (opens in a new tab)는 마크다운의 상위 집합으로, JSX (opens in a new tab)를 마크다운 파일 내에서 직접 작성할 수 있게 해줍니다. 이를 통해 동적 인터랙티브 기능을 추가하고 콘텐츠 내에 React 컴포넌트를 삽입할 수 있는 강력한 방법을 제공합니다.
Next.js는 애플리케이션 내의 로컬 MDX 콘텐츠와 서버에서 동적으로 가져오는 원격 MDX 파일을 모두 지원할 수 있습니다. Next.js 플러그인은 마크다운과 React 컴포넌트를 HTML로 변환하는 작업을 처리하며, Server Components(기본 App Router)에서도 사용을 지원합니다.
참고: 완전한 작동 예제를 보려면 Portfolio Starter Kit (opens in a new tab) 템플릿을 참조하세요.
Install dependencies
@next/mdx
패키지와 관련 패키지는 Next.js가 마크다운과 MDX를 처리할 수 있도록 구성하는 데 사용됩니다. 이 패키지는 로컬 파일에서 데이터를 소싱하여 /pages
또는 /app
디렉토리 내에서 .md
또는 .mdx
확장자를 가진 페이지를 생성할 수 있습니다.
다음 패키지를 설치하여 Next.js에서 MDX를 렌더링하세요:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Configure next.config.mjs
프로젝트 루트에 있는 next.config.mjs
파일을 업데이트하여 MDX를 사용하도록 구성합니다:
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// 마크다운 및 MDX 파일을 포함하도록 `pageExtensions`를 구성합니다
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// 선택적으로 다른 Next.js 구성 추가 가능
}
const withMDX = createMDX({
// 원한다면 여기에서 마크다운 플러그인을 추가하세요
})
// MDX 구성과 Next.js 구성을 병합합니다
export default withMDX(nextConfig)
이렇게 하면 .md
및 .mdx
파일이 애플리케이션 내에서 페이지, 라우트 또는 가져오기(import)로 작동할 수 있습니다.
Add an mdx-components.tsx
file
글로벌 MDX 컴포넌트를 정의하기 위해 프로젝트 루트에 mdx-components.tsx
(또는 .js
) 파일을 생성합니다. 예를 들어, pages
또는 app
과 같은 수준에 생성하거나, 해당되는 경우 src
폴더 안에 생성합니다.
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
}
}
export function useMDXComponents(components) {
return {
...components,
}
}
참고:
mdx-components.tsx
는 App Router와 함께@next/mdx
를 사용하기 위해 필수적이며, 없이는 작동하지 않습니다.mdx-components.tsx
파일 규칙에 대해 더 알아보세요.- 사용자 정의 스타일 및 컴포넌트 사용에 대해 알아보세요.
Rendering MDX
파일 기반 라우팅을 사용하여 MDX를 렌더링하거나 다른 페이지에 MDX 파일을 가져올 수 있습니다.
Using file based routing
파일 기반 라우팅을 사용할 때, MDX 페이지를 다른 페이지처럼 사용할 수 있습니다.
/pages
디렉토리에 새로운 MDX 페이지를 생성합니다:
my-project
|── mdx-components.(tsx/js)
├── pages
│ └── mdx-page.(mdx/md)
└── package.json
이 파일들에서 MDX를 사용할 수 있으며, MDX 페이지 내에서 React 컴포넌트를 직접 가져올 수 있습니다:
import { MyComponent } from 'my-component'
# Welcome to my MDX page!
This is some **bold** and _italics_ text.
This is a list in markdown:
- One
- Two
- Three
Checkout my React component:
<MyComponent />
/mdx-page
경로로 이동하면 렌더링된 MDX 페이지가 표시됩니다.
Using imports
/pages
디렉토리에 새로운 페이지를 만들고 원하는 곳에 MDX 파일을 생성합니다:
my-project
├── pages
│ └── mdx-page.(tsx/js)
├── markdown
│ └── welcome.(mdx/md)
|── mdx-components.(tsx/js)
└── package.json
이 파일들에서 MDX를 사용할 수 있으며, MDX 페이지 내에서 React 컴포넌트를 직접 가져올 수 있습니다:
import { MyComponent } from 'my-component'
# Welcome to my MDX page!
This is some **bold** and _italics_ text.
This is a list in markdown:
- One
- Two
- Three
Checkout my React component:
<MyComponent />
페이지 안에 MDX 파일을 가져와서 콘텐츠를 표시합니다:
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
/mdx-page
경로로 이동하면 렌더링된 MDX 페이지가 표시됩니다.
Using custom styles and components
마크다운은 렌더링될 때 네이티브 HTML 요소로 매핑됩니다. 예를 들어, 다음 마크다운을 작성하면:
## This is a heading
This is a list in markdown:
- One
- Two
- Three
다음 HTML이 생성됩니다:
<h2>This is a heading</h2>
<p>This is a list in markdown:</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
마크다운을 스타일링하려면 생성된 HTML 요소에
매핑되는 사용자 정의 컴포넌트를 제공할 수 있습니다. 스타일과 컴포넌트는 전역, 로컬, 공유 레이아웃으로 구현할 수 있습니다.
Global styles and components
mdx-components.tsx
에 스타일과 컴포넌트를 추가하면 애플리케이션의 모든 MDX 파일에 영향을 미칩니다.
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// 이 파일을 사용하여 MDX 파일에서 사용할 사용자 정의 React 컴포넌트를 제공할 수 있습니다.
// 원하는 모든 React 컴포넌트를 가져와 사용할 수 있으며, 인라인 스타일, 다른 라이브러리의 컴포넌트 등을 포함할 수 있습니다.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// 스타일을 추가하기 위해 내장된 컴포넌트를 사용자 정의할 수 있습니다.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
/>
),
...components,
}
}
import Image from 'next/image'
// 이 파일을 사용하여 MDX 파일에서 사용할 사용자 정의 React 컴포넌트를 제공할 수 있습니다.
// 원하는 모든 React 컴포넌트를 가져와 사용할 수 있으며, 인라인 스타일, 다른 라이브러리의 컴포넌트 등을 포함할 수 있습니다.
export function useMDXComponents(components) {
return {
// 스타일을 추가하기 위해 내장된 컴포넌트를 사용자 정의할 수 있습니다.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
Local styles and components
특정 페이지에 로컬 스타일과 컴포넌트를 적용하려면, 가져온 MDX 컴포넌트에 이를 전달하여 사용할 수 있습니다. 이는 전역 스타일 및 컴포넌트와 병합되며 이를 재정의합니다.
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
Shared layouts
MDX 페이지 주변에 레이아웃을 공유하려면 레이아웃 컴포넌트를 생성하세요:
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// 여기에서 모든 공유 레이아웃 또는 스타일을 생성하세요
return <div style={{ color: 'blue' }}>{children}</div>
}
export default function MdxLayout({ children }) {
// 여기에서 모든 공유 레이아웃 또는 스타일을 생성하세요
return <div style={{ color: 'blue' }}>{children}</div>
}
그런 다음, 레이아웃 컴포넌트를 MDX 페이지로 가져와 MDX 콘텐츠를 레이아웃으로 감싸고 내보냅니다:
import MdxLayout from '../components/mdx-layout'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
Using Tailwind typography plugin
애플리케이션 스타일링에 Tailwind (opens in a new tab)를 사용하는 경우, @tailwindcss/typography
플러그인 (opens in a new tab)을 사용하면 Tailwind 구성 및 스타일을 마크다운 파일에서 재사용할 수 있습니다.
이 플러그인은 마크다운과 같은 소스에서 가져온 콘텐츠 블록에 타이포그래픽 스타일을 추가하는 prose
클래스를 제공합니다.
Tailwind typography 설치 (opens in a new tab) 및 공유 레이아웃 사용하여 원하는 prose
를 추가하세요.
MDX 페이지 주변에 레이아웃을 공유하려면 레이아웃 컴포넌트를 생성하세요:
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// 여기에서 모든 공유 레이아웃 또는 스타일을 생성하세요
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
export default function MdxLayout({ children }) {
// 여기에서 모든 공유 레이아웃 또는 스타일을 생성하세요
return (
<div
className="prose prose-headings:mt-8 prose-headings:font-semibold prose
-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white"
>
{children}
</div>
)
}
그런 다음, 레이아웃 컴포넌트를 MDX 페이지로 가져와 MDX 콘텐츠를 레이아웃으로 감싸고 내보냅니다:
import MdxLayout from '../components/mdx-layout'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
Frontmatter
Frontmatter는 페이지에 대한 데이터를 저장하는 데 사용되는 YAML과 같은 키/값 쌍입니다. @next/mdx
는 기본적으로 frontmatter를 지원하지 않지만, MDX 콘텐츠에 frontmatter를 추가하는 다양한 솔루션이 있습니다:
- remark-frontmatter (opens in a new tab)
- remark-mdx-frontmatter (opens in a new tab)
- gray-matter (opens in a new tab)
@next/mdx
는 다른 JavaScript 컴포넌트처럼 export를 사용할 수 있도록 해줍니다:
export const metadata = {
author: 'John Doe',
}
# Blog post
이제 MDX 파일 외부에서 메타데이터를 참조할 수 있습니다:
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata': metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata': metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
이의 일반적인 사용 사례는 MDX 컬렉션을 반복하고 데이터를 추출하려는 경우입니다. 예를 들어, 모든 블로그 게시물에서 블로그 인덱스 페이지를 생성하는 것입니다. Node의 fs
모듈 (opens in a new tab)이나 globby (opens in a new tab)와 같은 패키지를 사용하여 게시물 디렉토리를 읽고 메타데이터를 추출할 수 있습니다.
참고:
fs
,globby
등을 사용하는 것은 서버 측에서만 가능합니다.- 완전한 작동 예제를 보려면 Portfolio Starter Kit (opens in a new tab) 템플릿을 참조하세요.
Remark and Rehype Plugins
선택적으로 remark
및 rehype
플러그인을 제공하여 MDX 콘텐츠를 변환할 수 있습니다.
예를 들어, remark-gfm
을 사용하여 GitHub Flavored Markdown을 지원할 수 있습니다.
remark
및 rehype
생태계는 ESM 전용이므로 구성 파일로 next.config.mjs
를 사용해야 합니다.
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// MDX 파일을 포함하도록 `pageExtensions`를 구성합니다
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// 선택적으로 다른 Next.js 구성 추가 가능
}
const withMDX = createMDX({
// 원한다면 여기에서 마크다운 플러그인을 추가하세요
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// MDX 구성과 Next.js 구성을 병합합니다
export default withMDX(nextConfig)
Remote MDX
MDX 파일이나 콘텐츠가 다른 곳에 있는 경우, 서버에서 동적으로 가져올 수 있습니다. 이는 별도의 로컬 폴더, CMS, 데이터베이스 또는 다른 곳에 저장된 콘텐츠에 유용합니다. 이 용도로 인기 있는 커뮤니티 패키지는 next-mdx-remote
(opens in a new tab)입니다.
참고: 신중하게 사용하세요. MDX는 JavaScript로 컴파일되며 서버에서 실행됩니다. 신뢰할 수 있는 소스에서만 MDX 콘텐츠를 가져와야 하며, 그렇지 않으면 원격 코드 실행(RCE)로 이어질 수 있습니다.
다음 예제는 next-mdx-remote
를 사용합니다:
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function RemoteMdxPage({ mdxSource }: Props) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// MDX 텍스트 - 로컬 파일, 데이터베이스, CMS, fetch 등에서 가져올 수 있습니다...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { mdxSource } }
}
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
export default function RemoteMdxPage({ mdxSource }) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// MDX 텍스트 - 로컬 파일, 데이터베이스, CMS, fetch 등에서 가져올 수 있습니다...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { mdxSource } }
}
/mdx-page-remote
경로로 이동하면 렌더링된 MDX가 표시됩니다.
Deep Dive: How do you transform markdown into HTML?
React는 본래 마크다운을 이해하지 못합니다. 마크다운 평문은 먼저 HTML로 변환되어야 합니다. 이는 remark
와 rehype
를 사용하여 수행할 수 있습니다.
remark
는 마크다운을 중심으로 한 도구 생태계입니다. rehype
는 HTML을 중심으로 한 생태계입니다. 예를 들어, 다음 코드 스니펫은 마크다운을 HTML로 변환합니다:
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
main()
async function main() {
const file = await unified()
.use(remarkParse) // 마크다운 AST로 변환
.use(remarkRehype) // HTML AST로 변환
.use(rehypeSanitize) // HTML 입력을 소독
.use(rehypeStringify) // AST를 직렬화된 HTML로 변환
.process('Hello, Next.js!')
console.log(String(file)) // <p>Hello, Next.js!</p>
}
remark
및 rehype
생태계는 문법 강조 (opens in a new tab), 제목 링크 추가 (opens in a new tab), 목차 생성 (opens in a new tab) 등 다양한 플러그인을 포함합니다.
위에서 설명한 것처럼 @next/mdx
를 사용할 때는 직접 remark
나 rehype
를 사용할 필요가 없습니다. 이는 내부적으로 처리됩니다. 여기서는 @next/mdx
패키지가 내부적으로 수행하는 작업에 대한 깊은 이해를 위해 설명하고 있습니다.
Using the Rust-based MDX compiler (experimental)
Next.js는 Rust로 작성된 새로운 MDX 컴파일러를 지원합니다. 이 컴파일러는 아직 실험적이며, 프로덕션 사용을 권장하지 않습니다. 새로운 컴파일러를 사용하려면, withMDX
에 전달할 때 next.config.js
를 구성해야 합니다:
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})
mdxRs
는 mdx 파일을 변환하는 방법을 구성하기 위해 객체도 허용합니다.
module.exports = withMDX({
experimental: {
mdxRs: {
jsxRuntime?: string // 커스텀 jsx 런타임
jsxImportSource?: string // 커스텀 jsx import 소스,
mdxType?: 'gfm' | 'commonmark' // 구문 분석 및 변환에 사용할 mdx 구문 종류 구성
},
},
})
참고:
이 옵션은 Turbopack(
next dev --turbo
)을 사용하여 마크다운 및 MDX를 처리할 때 필요합니다.