image

Next.js에 mdx파일 적용하기

태그
Next.jsJavascript
상세설명Next.js에 mdx 적용, 파일 불러오기
작성일자2024.02.24

프로젝트 중 mdx 파일을 불러와야 하는 상황이 생겨 Next.js (14버전)에서 어떻게 불러오는지 알아보았다.

MDX in NextJS

MDX 확장자는 마크다운(MD)과 JSX가 결합되어 마크다운 컨텐츠를 리액트 내에서 컴포넌트 형태로 export하거나, JSX컴포넌트를 MDX파일 내에 import 할 수 있도록 한다. 정적 컨텐츠를 컴포넌트화 시키는 특성 때문에 SSG를 지원하는 프레임워크(Gatsby, Next)에서는 MDX를 위한 플러그인이 잘 지원되고 있다.

먼저 Next.js에서 mdx파일을 불러오기 위해 하위 packages 을 설치해야 한다.

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

https://nextjs.org/docs/pages/building-your-application/configuring/mdx

설치 후 next.config.js 도 변경한다.

import remarkGfm from "remark-gfm";
import createMDX from "@next/mdx";

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Configure `pageExtensions`` to include MDX files
  pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
  // Optionally, add any other Next.js config below
};

const withMDX = createMDX({
  // Add markdown plugins here, as desired
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
});

// Merge MDX config with Next.js config
export default withMDX(nextConfig);

Next.js 의 /app 디렉토리에서 사용하는 방법

  • mdx 파일을 원하는 곳에 생성한다. (필자는 프로젝트 루트에 만들었다.)
  • image

    test.mdx 예시

    ---
    title: 제목1
    author: 관리자
    category: 공지사항
    date: "2023.07.09"
    ---
    
    ## What is Lorem Ipsum?
    
    Lorem Ipsum is dummy text.
    

  • 마크다운 파일을 읽기 위해 gray-matter를 설치한다.
  • npm i gray-matter

    리스트 페이지

  • app router에서는 getStaticProps를 사용하지 못하기 때문에 Markdown 파일들이 있는 디렉토리 경로를 찾아 파일들을 불러와 리스트 페이지를 만든다.
  • import Image from "next/image"; 
    import Link from "next/link"; 
    import path from "path"; // 파일 경로 조작을 위한 Node.js 내장 모듈
    import matter from "gray-matter"; // Markdown 파일의 front matter(metadata)를 추출하는 라이브러리
    const fs = require("fs"); // 파일 시스템 모듈
    
    export default function Test() {
    
      // 포스트 데이터를 정렬하여 가져오는 함수를 정의합니다.
      function getSortedPostsData() {
        const postsDirectory = path.join(process.cwd(), "_newsposts"); // Markdown 파일들이 있는 디렉토리 경로
        const fileNames = fs.readdirSync(postsDirectory); // 해당 디렉토리 내 파일 목록을 동기적으로 읽어옵니다.
        const allPostsData = fileNames.map((fileName) => {
          const id = fileName.replace(/\.mdx$/, ""); // 파일 이름에서 확장자를 제거하여 id로 사용합니다.
    
          // Markdown 파일을 읽어옵니다.
          const fullPath = path.join(postsDirectory, fileName); // 전체 파일 경로
          const fileContents = fs.readFileSync(fullPath, "utf8"); // 파일 내용을 UTF-8 형식으로 읽어옵니다.
          const matterResult = matter(fileContents); // gray-matter 라이브러리를 사용하여 front matter를 추출합니다.
    
          return {
            id,
            ...matterResult.data, // front matter 데이터를 반환합니다.
          };
        });
    
        // 날짜를 기준으로 포스트 데이터를 정렬합니다.
        return allPostsData.sort((a, b) => {
          if (a.date < b.date) {
            return 1; // a의 날짜가 더 작으면 b 앞으로 정렬합니다.
          } else {
            return -1; // b의 날짜가 더 작으면 a 앞으로 정렬합니다.
          }
        });
      }
    
      // 정렬된 포스트 데이터를 가져옵니다.
      const postDatas = getSortedPostsData();
    
      return (
        <section className="commu_board w-[600px] border border-black mx-auto mt-10 p-4">
          <div>
            <ul>
              {postDatas.map((data, index) => (
                <li key={data.id} className="flex_line border-b border-[#dbdbdb]">
                  <div className="w-[110px]">{index + 1}</div>
                  <div className="w-[110px]">{data.date}</div>
                  <div className="w-[110px]">{data.category}</div>
                  <Link
                    href={`/test/${data.id}`}
                    className="flex-1 text-left py-8 leading-10 font-semibold text-[32px] overflow-hidden text-ellipsis whitespace-nowrap"
                  >
                    {data.title}
                  </Link>
                </li>
              ))}
            </ul>
          </div>
        </section>
      );
    }

    image

    리스트 클릭 시 상세페이지

  • 리스트페이지에서 이동하는 href={`/test/${data.id}`}에 따라 data.id 값을 props로 가져오고 해당파일만 가져온다.
  • import Link from "next/link";
    import path from "path";
    import matter from "gray-matter";
    const fs = require("fs");
    
    export default function Mdx(props) {
      function getSortedPostsData() {
        const postsDirectory = path.join(process.cwd(), "_newsposts");
        const fileNames = fs
          .readdirSync(postsDirectory)
          .filter((fileName) => fileName === `${props.params.id}.mdx`);
        const allPostsData = fileNames.map((fileName) => {
          const id = fileName.replace(/\.mdx$/, "");
    
          const fullPath = path.join(postsDirectory, fileName);
          const fileContents = fs.readFileSync(fullPath, "utf8");
          const matterResult = matter(fileContents);
    
          return {
            id,
            content: matterResult.content,
            ...matterResult.data,
          };
        });
        return allPostsData;
      }
    
      const postDatas = getSortedPostsData();
    
      return (
        <section className="commu_board w-[600px] mx-auto mt-10 p-4">
          <div>
            <ul>
              {postDatas.map((data, index) => (
                <li key={data.id}>
                  <div className="flex_line border-b border-[#dbdbdb]">
                    <div className="w-[110px]">{index + 1}</div>
    
                    <Link
                      href={`/test/${data.id}`}
                      className="flex-1 text-left py-8 leading-10 font-semibold text-[32px] overflow-hidden text-ellipsis whitespace-nowrap"
                    >
                      {data.title}
                    </Link>
                    <div className="w-[110px]">{data.date}</div>
                    <div className="w-[110px]">{data.category}</div>
                  </div>
                  <div className="m-4 text-center">{data.content}</div>
                </li>
              ))}
            </ul>
          </div>
        </section>
      );
    }
    

    *** 동적 파일인 [id] 값 가져오기

    Next.js의 app router로 변경되면서 현재 주소를 알려면 “use client” 를 사용해 import { usePathname } from 'next/navigation’ 해야되서 당황했는데 동적 라우팅 파일인 [id].jsx 에서 props 를 가져오면 현재 주소 값 및 쿼리 문자열 등의 정보를 얻을 수 있다.

    export default function Mdx(props) {
    	console.log(props)
      // 결과 : { params: { id: 'test' }, searchParams: {} }
    	return <></>
    }