프로젝트 중 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 디렉토리에서 사용하는 방법
test.mdx 예시
--- title: 제목1 author: 관리자 category: 공지사항 date: "2023.07.09" --- ## What is Lorem Ipsum? Lorem Ipsum is dummy text.
npm i gray-matter
리스트 페이지
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> ); }
리스트 클릭 시 상세페이지
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 <></> }