1
2
3
4
5
6
| # โจ Next.js์์ MDX ์ฌ์ฉํ๊ธฐ
## ๐ฆ MDX ๊ด๋ จ ํจํค์ง ์ค์น
yarn add @next/mdx @mdx-js/loader @mdx-js/react
yarn add --dev @types/mdx
|
@next/mdx
: Next.js์์ MDX๋ฅผ ์ง์ํ๊ธฐ ์ํ ๊ณต์ ํจํค์ง@mdx-js/loader
: MDX ๋ฌธ์๋ฅผ ์ปดํ์ผํ๋ Webpack ๋ก๋@mdx-js/react
: MDX ๋ฌธ์ ๋ด์์ React ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.@types/mdx
: TypeScript ํ๋ก์ ํธ์์ MDX ํ์ผ์ ๋ํ ํ์
์ง์์ ์ ๊ณตํฉ๋๋ค.remark-prism
: ๋งํฌ๋ค์ด ๋ด ์ฝ๋ ๋ธ๋ก์ ํ์ด๋ผ์ดํ
ํ๊ธฐ ์ํ ํจํค์ง (์ ํ์ฌํญ)
๐ง next.config.mjs
์์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| tsxCopy code
import createMDX from '@next/mdx';
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
experimental: {
appDir: true,
},
};
const withMDX = createMDX({
extension: /\.(md|mdx)$/,
options: {
// ํ์ํ ๊ฒฝ์ฐ MDX ์ต์
๋ฐ ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
},
});
export default withMDX(nextConfig);
|
pageExtensions
๋ถ๋ถ์ md
, mdx
๋ฅผ ์ถ๊ฐํด์ค๋๋ค.withMDX
๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
โ๏ธ ts.config.json
์ค์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| jsonCopy code
{
"compilerOptions": {
// ํ์ํ ์ต์
์ถ๊ฐ
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.md",
"**/*.mdx"
]
}
|
include
๋ถ๋ถ์ md
ํ์ผ๊ณผ mdx
ํ์ผ์ ์ถ๊ฐํด์ค๋๋ค.
๐ mdx-components.tsx
์์ฑ
mdx-components.tsx
ํ์ผ์ ์์ฑํ์ฌ ๊ฐ๋ณ ์ปค์คํ
์ค์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| tsxCopy code
import type { MDXComponents } from "mdx/types";
export const HighlightedText = ({ children }: { children: React.ReactNode }) => {
return <span style={{ backgroundColor: "yellow" }}>{children}</span>;
};
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
h1: (props) => <h1 style={{ color: "blue" }} {...props} />,
HighlightedText,
};
}
|
๐ mdx-provider.tsx
์์ฑ
1
2
3
4
5
6
7
8
9
| tsxCopy code
import { MDXProvider } from "@mdx-js/react";
import { useMDXComponents } from "./mdx-components";
export function MDXComponentsProvider({ children }: { children: React.ReactNode }) {
const components = useMDXComponents({});
return <MDXProvider components={components}>{children}</MDXProvider>;
}
|
provider
๋ .md
, .mdx
ํ์ผ์ ํ์ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค๋๋ค.
๐ .md
๋๋ .mdx
ํ์ผ ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
| mdCopy code
# Welcome.mdx
This is a blog post written in **MDX**.
- List item 1
- List item 2
<HighlightedText> This text is highlighted in MDX! </HighlightedText>```python
print('hello')
|
๐ .tsx
ํ์ด์ง ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
## ๐ ํ์ด์ง ์์ฑ
```tsx
"use client";
import Welcome from "@/markdown/welcome.mdx";
import Test from "@/markdown/test.md";
import { MDXComponentsProvider } from "@/mdx/mdx-provider";
export default function Page() {
return (
<MDXComponentsProvider>
<Welcome />
<Test />
</MDXComponentsProvider>
);
}
|
๐๏ธ ๋งํฌ๋ค์ด ์ฝ๋ ํ์ด๋ผ์ดํ
์์
remark-gfm
์ ๋งํฌ๋ค์ด ํ์ผ์์ ํ๋ ๋ฆฌ์คํธ๋ฅผ ๋ ๋ค์ฑ๋กญ๊ฒ ํํํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
๐ง next.config.mjs
์์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import createMDX from "@next/mdx";
import rehypeHighlight from "rehype-highlight";
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
experimental: {
appDir: true
}
};
const withMDX = createMDX({
extension: /\.(md|mdx)$/,
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight] // rehype ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
}
});
export default withMDX(nextConfig);
|
remark-gfm
์ options
ํญ๋ชฉ์ ์ถ๊ฐ
๐ ์ฝ๋ ํ์ด๋ผ์ดํ
๋ฐฉ๋ฒ ์ ํ
๋ฐฉ๋ฒ 1: ๐๏ธ rehype-highlight & prismjs
์ค์น
1
| yarn add rehype-highlight prismjs
|
globals.css
์ ์ถ๊ฐ
1
2
| /* globals.css */
@import "prismjs/themes/prism.css";
|
next.config.mjs
์์
1
2
3
4
| options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight],
}
|
๋ฐฉ๋ฒ 2: ๐ผ๏ธ react-syntax-highlighter
์ค์น
1
2
| yarn add react-syntax-highlighter
yarn add @types/react-syntax-highlighte
|
code-block
์ปดํฌ๋ํธ ์ ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { [ํ
๋ง์ ์๋ถ๋ถ] as theme } from 'react-syntax-highlighter/dist/esm/styles/prism';
interface CodeBlockProps {
language: string;
value: string;
}
export const CodeBlock = ({ language, value }: CodeBlockProps) => {
return (
<SyntaxHighlighter language={language} style={theme}>
{value}
</SyntaxHighlighter>
);
};
|
mdx-components.tsx
์์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| import React from "react";
import type { MDXComponents } from "mdx/types";
import { CodeBlock } from "./code-block";
export const HighlightedText = ({
children
}: {
children: React.ReactNode;
}) => {
return <span style={{ backgroundColor: "yellow" }}>{children}</span>;
};
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
h1: (props) => <h1 style={{ color: "blue" }} {...props} />,
code: (props) => {
const { className, children } = props as any;
const match = /language-(\w+)/.exec(className || "");
return match ? (
<CodeBlock language={match[1]} value={String(children).trim()} />
) : (
<code {...props} />
);
},
HighlightedText
};
}
|
์ด ๊ณผ์ ์ ๊ฑฐ์น๋ฉด ์ฝ๋๊ฐ ๊น๋ํ๊ฒ ๋๋ค.