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

2024. 5. 9. 18:53ยทStack/Next.js
๋ฐ˜์‘ํ˜•

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

# โœจ 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 ์ˆ˜์ •

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 ์„ค์ •

jsonCopy code
{
  "compilerOptions": {
    // ํ•„์š”ํ•œ ์˜ต์…˜ ์ถ”๊ฐ€
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.md",
    "**/*.mdx"
  ]
}
  • include ๋ถ€๋ถ„์— md ํŒŒ์ผ๊ณผ mdx ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

๐Ÿ›  mdx-components.tsx ์ž‘์„ฑ

mdx-components.tsx ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜์—ฌ ๊ฐœ๋ณ„ ์ปค์Šคํ…€ ์„ค์ •์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

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 ์ž‘์„ฑ

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 ํŒŒ์ผ ์ž‘์„ฑ

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 ํŽ˜์ด์ง€ ์ž‘์„ฑ


## ๐Ÿ“ƒ ํŽ˜์ด์ง€ ์ž‘์„ฑ

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

remark-gfm์€ ๋งˆํฌ๋‹ค์šด ํŒŒ์ผ์—์„œ ํ‘œ๋‚˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋” ๋‹ค์ฑ„๋กญ๊ฒŒ ํ‘œํ˜„ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

๐Ÿ”ง next.config.mjs ์ˆ˜์ •

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

์„ค์น˜

yarn add rehype-highlight prismjs

globals.css์— ์ถ”๊ฐ€

/* globals.css */
@import "prismjs/themes/prism.css";

next.config.mjs ์ˆ˜์ •

options: {
  remarkPlugins: [remarkGfm],
  rehypePlugins: [rehypeHighlight],
}

๋ฐฉ๋ฒ• 2: ๐Ÿ–ผ๏ธ react-syntax-highlighter

์„ค์น˜

yarn add react-syntax-highlighter
yarn add @types/react-syntax-highlighte

code-block ์ปดํฌ๋„ŒํŠธ ์ •์˜

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 ์ˆ˜์ •

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

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

๋ฐ˜์‘ํ˜•

'Stack > Next.js' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Button์œผ๋กœ Resizable ๊ตฌํ˜„ํ•˜๊ธฐ  (2) 2024.06.04
Draggable ๊ตฌํ˜„ํ•˜๊ธฐ  (0) 2024.06.04
Next.js metadata ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์˜ ๋ณ€  (1) 2024.05.14
Next Image ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•  (1) 2024.05.14
MongoDB ์™€ Next.JS ์—ฐ๊ฒฐํ•˜๊ธฐ  (0) 2024.05.01
'Stack/Next.js' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • Draggable ๊ตฌํ˜„ํ•˜๊ธฐ
  • Next.js metadata ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์˜ ๋ณ€
  • Next Image ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  • MongoDB ์™€ Next.JS ์—ฐ๊ฒฐํ•˜๊ธฐ
WHITE_FROST
WHITE_FROST
๊ฐœ๋ฐœ๊ณต๋ถ€๋ฆฌ๋ทฐ๋ธ”๋กœ๊ทธ
    ๋ฐ˜์‘ํ˜•
  • WHITE_FROST
    ํ•˜์–€ํ•˜์–€IT
    WHITE_FROST
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (119)
      • Stack (43)
        • Next.js (7)
        • React (12)
        • React-Native (15)
        • TypeScript (0)
        • Python (2)
        • JavaScript (2)
        • Android (1)
        • DB (2)
        • JAVA (1)
      • Obsidian (1)
      • AI (3)
      • AI Tools (0)
      • Tools (0)
      • Mac (0)
      • Error (7)
      • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ฆฌ (6)
      • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฌธ์ œํ’€์ด (46)
      • ๊ณต๋ถ€์ผ์ƒ (4)
      • ๊ฐœ๋ฐœ ๋„๊ตฌ & ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (0)
      • ์ •๋ณด์ฒ˜๋ฆฌ๊ธฐ์‚ฌ (0)
      • ๊ธฐํƒ€ (6)
      • Tip (2)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฏธ๋””์–ด๋กœ๊ทธ
    • ์œ„์น˜๋กœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    Expo
    Python
    react-native
    mongoDB Atlas
    javascript
    ReactHook
    ์˜ค๋ธ”์™„
    boj
    react
    react-native-maps
    mongodb cloud
    ๋ฆฌ์•กํŠธ๋„ค์ดํ‹ฐ๋ธŒ
    ์ฝ”ํ…Œ์ค€๋น„
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ์ฝ”ํ…Œ
    React Hooks
    ios
    hooks
    Next.js
    d1
    SWEA
    ๋ฐฑ์ค€
    error
    reactnative
    React-Native cli
    ํ‹ฐ์Šคํ† ๋ฆฌ์ฑŒ๋ฆฐ์ง€
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ
    java
    D2
    ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.5
WHITE_FROST
๐Ÿ“˜ Next.js ์— markdown ์„ค์น˜ ํ•˜๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”