next.js로 블로그 프로젝트를 구성할 때 가장 많이 고민하게 되는 부분은 바로 마크다운의 처리입니다.
next.js에서 서포트하는 mdx 관리 방식이 존재하긴 하지만 특성상 많은 요구사항을 수용하지 못합니다.
따라서 마크다운을 관리하는 가장 좋은 방법이라고 할만한게 정립이 되어있지 않은 상태로
contentlayer, velite, next-mdx-remote 정도의 솔루션들이 존재하는 형태입니다.
이번 글에서는 contentlayer에서 next-mdx-remote로 전환한 후기와 이유를 설명합니다.
왜 contentlayer에서 next-mdx-remote로 옮겨야 했는가?
커뮤니티 지원이 끊겨 버린지 약 1년이 되었다
contentlayer는 2024년 4월 13일 기준으로 10개월간 방치되어있는 상태였습니다.
2023년도까지는 활발히 업데이트 되며 커뮤니티의 관심을 받았지만 어느새 관리가 되지 않기 시작했고
swc 등 최근 next.js의 컴파일러 업데이트에 따라가지 못하는 모습을 보였습니다.
그래서 contentlayer로 구성된 next.js 프로젝트는 swcminify 옵션을 끄고 사용해야하는 경우가 생겼어요
https://github.com/contentlayerdev/contentlayer/issues/651
포스트를 작성하는 김에 이슈탭을 슬쩍 둘러보니 누군가가 메인테이너가 되겠다는 이슈가 있었습니다.
아마 실제로 저 이슈어가 메인테이너가 된다면 다시 유지보수 될 것이라는 기대를 할 수 있겠습니다만
제가 contentlayer에 대한 마음이 떨어진것은 커뮤니티 지원에 대한 문제만은 아니었습니다.
프로젝트의 핵심 구성이 contentlayer로 인해 더럽혀진다
저는 되도록이면 내 프로젝트가 외부 라이브러리를 세부사항으로 치부하길 바랍니다.
이 대전제를 이루기 위해서 저는 너무 자기 주장이 강한 라이브러리를 사용하길 즐기지 않습니다.
contentlayer는 간단한 설정만으로 타입안정성까지 제공하지만
해당 타입을 사용하기위해서 contentlayer에 깊숙히 의존하게 되는 문제를 함께 발생시킵니다.
next-mdx-remote를 선택한 이유
확실히 next.js 생태계에서 마크다운에 대한 선택지는 그리 풍부하지 않습니다.
위에서 짧막하게 언급한 velite는 아직 활발히 개발하고 있는 단계로 보이며
contentlayer는 메인테이너가 작업에 손을 놓은지 1년이 다되어가고
공식 서포트인 @next/mdx는 라우팅을 mdx로 할것을 강요합니다.
솔직히 이쯤되면 그냥 마크다운 말고 컴포넌트들을 잘 구성해서 jsx로 포스트를 쓸까하는 생각도 들더라구요
그나마 markdown-mdx-remote의 경우 실험적이지만 rsc에 대한 서포트가 진행되고 있었고
사용방식 자체가 contentlayer와 같이 라이브러리에 크게 종속되지않는 형태이기에
추후 다른 라이브러리로 마이그레이션이 용이할것이라고 판단하고 마이그레이션을 진행했습니다.
next-mdx-remote의 문서는 너무 빈약합니다.
사실 이런 라이브러리들은 문서화가 잘 되어있지 않은 경우가 많긴 합니다만
진짜 눈치껏 써야하는 상황은 라이브러리 사용자 입장에서 스트레스로 작용하기 쉽습니다.
저도 좀 스트레스 받았는데요 어느정도 필요한만큼은 정리가 되어서 글로 남깁니다.
yarn add next-mdx-remote
의존성을 설치합니다.
import { MDXRemote, compileMDX } from "next-mdx-remote/rsc";
app router에 대한 지원은 next-mdx-remote/rsc에서 찾아볼 수 있습니다.
export declare function compileMDX<TFrontmatter = Record<string, unknown>>({ source, options, components, }: MDXRemoteProps): Promise<CompileMDXResult<TFrontmatter>>;
/**
* Renders compiled source from next-mdx-remote/serialize.
*/
export declare function MDXRemote(props: MDXRemoteProps): Promise<React.ReactElement<any, string | React.JSXElementConstructor<any>>>;
타입 정의는 다음과 같아요
여기에서 MDXRemote는 컴포넌트인데요 rsc에서 가져온 MDXRemote는 서버컴포넌트에서 사용할 것으로 강제된다고 합니다.
compileMDX는 frontmatter를 추출해서 사용할 필요가 있는 경우 frontmatter를 추출하기 위한 유틸리티함수로 사용됩니다.
frontmatter가 뭐냐하면
마크다운에서 작성하는 이런 형태를 frontmatter라고 합니다.
사용 예시 한번 보면 이해가 아주 쉬워집니다.
import fs from "fs";
import path from "path";
import { MDXRemote, compileMDX } from "next-mdx-remote/rsc";
const filePath = path.join(process.cwd(), "posts", "post-01.mdx");
const markdownsource = fs.readFileSync(filePath, "utf8");
const { content, frontmatter } = await compileMDX({
source: markdownsource,
options: {
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [],
rehypePlugins: []
}
}
});
저는 mdx를 직접 프로젝트에 파일로 저장해두고 쓰기 때문에 fs 모듈을 이용해서 mdx를 읽어온 것입니다.
만약 마크다운을 디비에 저장해둔다면 디비에서 꺼내오시면 되겠습니다.
compileMDX에서는 options을 줄수가 있는데요 parseFrontmatter 옵션을 통해서 프론트매터를 파싱할지안할지 결정가능합니다.
compileMDX에 제네릭 넘겨서 프론트매터의 타입추론이 가능하게도 할 수 있으니 염두해두세요
content 부분에는 remark와 rehype을 넣은대로 적용된 jsx 컴포넌트가 존재합니다.
다만 크게 쓸일은 없으니 스킵하셔도 좋습니다.
const filePath = path.join(process.cwd(), "posts", "post-01.mdx");
const markdownsource = fs.readFileSync(filePath, "utf8");
<MDXRemote
source={markdownsource}
options={{
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [],
rehypePlugins: []
}
}}
/>
MDXRemote의 경우도 api가 다르지 않습니다.
다만 여기서 parseFrontmatter 옵션을 끄게되면 결과물에 프론트매터도 그대로 나오니까 true로 두는걸 추천드립니다.
추가로 MDXRemote의 경우에는 component라는 prop도 넘겨줄 수 있는데
이건 h1, h2와 같이 html 태그들에 대한 세세한 컴포넌트 정의들을 넘길 수 있는 옵션입니다.
마치며
고작 이정도 소개해주려고 글썼냐는 인상을 받으셨을 수도 있겠습니다.
근데 next-mdx-remote 문서를 한번 봐보세요
이런거만 보고 어떻게 써야할지 바로 캐치해서 MDXRemote 컴포넌트에도 parseFrontmatter란게있겠지?
같은 발상을 해낼 수 있는 분이라면 존경의 따봉 열개 드리겠습니다.
솔직히 next-mdx-remote도 커뮤니티 지원이 안정적이라는 느낌은 들지 않습니다만
추후 마이그레이션에 있어서는 contentlayer보다 훨씬 쉬울 것으로 기대됩니다.
문서가 너무 빈약해서 조금 고생했습니다만 충분히 시도할 가치는 있을 것 같네요
읽어주셔서 감사합니다.
만수무강하세요
'Next.js' 카테고리의 다른 글
next.js에서 react query가 필요할까? (8) | 2024.09.14 |
---|---|
next.js13 app dir mui tailwindcss starter kit 보일러 플레이트 (0) | 2023.08.18 |
next-auth를 이용해 Google 로그인 구현하기(기초) (1) | 2023.05.31 |
Next.js에서 에러를 핸들링 하는 방법 (0) | 2023.05.27 |
Next.js에서 환경변수 .env를 클라이언트 사이드에서도 사용하려면..? (0) | 2023.05.05 |