ํฌ์ŠคํŠธ

๐Ÿ“˜Next.js ์— markdown ์„ค์น˜ ํ•˜๊ธฐ

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
1
yarn add remark-gfm

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
  };
}

์ด ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•˜๊ฒŒ ๋œ๋‹ค.

์ด ๊ธฐ์‚ฌ๋Š” ์ €์ž‘๊ถŒ์ž์˜ CC BY 4.0 ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

ยฉ AppleTrick. ์ผ๋ถ€ ๊ถŒ๋ฆฌ ๋ณด์œ 

Powered by Jekyll with Chirpy theme