LogoSEO Jing
  • All Posts
  • SEO Jing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

DevLogSEO JingInsight

MDX 관련 이슈 노트

2026년 3월 15일·5분 읽기

배경

블로그 디테일 페이지에서 content/ 디렉토리의 .mdx 파일을 읽어 React 컴포넌트로 렌더링해야 했다.


검토한 라이브러리들

1. @mdx-js/mdx — MDX 공식 컴파일러

MDX를 JavaScript로 컴파일하는 핵심 라이브러리. compile()로 MDX 문자열을 JS 코드로 변환하고, run()이나 evaluate()로 실행하여 React 컴포넌트를 얻는다. 프레임워크 비의존적이지만, run() 내부에서 new Function() (사실상 eval)을 사용하여 컴파일된 JS를 실행한다. 콘텐츠가 본인이 작성한 로컬 파일이면 실질적 위험은 낮지만, 런타임 코드 실행이라는 본질적 보안 우려가 있다.

2. @mdx-js/rollup — Vite/Rollup 빌드타임 플러그인

MDX 파일을 빌드타임에 JS 모듈로 컴파일하는 Rollup 플러그인. vinext가 이 플러그인을 자동 감지하여 주입하는 기능을 내장하고 있었다. import()로 MDX 파일을 가져오면 빌드 시 컴파일되므로 런타임 eval이 없어 보안상 이상적이었지만,

동적 라우트([...slug])에서 import()의 경로가 빌드타임에 정적 분석이 불가능

하여 동작하지 않았다. Vite의 동적 import는 glob 패턴이 빌드 시 확정 가능해야 하는데, catch-all 라우트의 slug는 런타임에만 결정되기 때문이다.

  1. next-mdx-remote — HashiCorp의 원격 MDX 렌더링 라이브러리

Next.js App Router의 React Server Component(RSC) 환경에서 MDX를 렌더링하기 위한 라이브러리. 내부적으로 @mdx-js/mdx의 compile()을 사용하지만, 이를 서버 컴포넌트 친화적인 API로 감싸 제공한다.

4. Contentlayer / Velite — 빌드타임 콘텐츠 레이어

설정 파일을 작성하면 빌드 시 MDX를 자동으로 타입 안전한 JSON/JS로 변환해주는 도구들. DX는 좋지만 설정이 무겁고, Next.js 외 환경(vinext)에서 호환성 이슈가 있어 제외했다.


최종 선택: next-mdx-remote v6.0.0

선택 이유:
  • 기존 패턴과의 일관성: 프로젝트에서 이미 fs.readFileSync + gray-matter로 MDX 파일을 읽고 frontmatter를 파싱하는 패턴이 있었다. next-mdx-remote는 이 흐름에 자연스럽게 붙는다.
  • 보안 개선 (v6.0.0): 2026년 2월 CVE-2026-0969가 공개되었다. v4.3.0~v5.0.0에서 MDX 내 JS 표현식이 SSR 중 서버 권한으로 실행되는 RCE 취약점이었다. v6.0.0에서 blockJS: true가 기본값으로 설정되어 JS 표현식 실행이 차단된다.
  • vinext 호환성: vinext는 Next.js API surface를 Vite 위에서 재구현한 프레임워크다. next-mdx-remote/rsc의 MDXRemote는 내부적으로 @mdx-js/mdx의 compile만 사용하고 Next.js 전용 API에 의존하지 않아 vinext RSC 환경에서 정상 동작했다.
  • 서버 전용 실행: MDXRemote는 async 서버 컴포넌트이므로 컴파일된 코드가 클라이언트에 노출되지 않는다.

구현 과정에서 마주친 문제들

1. [...slug] 라우트 404 문제

vinext에서 catch-all 라우트를 생성한 후 dev 서버가 404를 반환했다. 원인은 vinext의 라우트 캐싱이었고, 서버를 완전히 재시작하면 해결되었다. HMR은 새 디렉토리 추가를 감지하지 못한다.

2. import.meta.dirname 경로 계산 오류

처음에 ../../../../content로 4단계를 올라갔는데, 실제 파일 위치가 apps/web/src/app/(main)/blog/[...slug]/page.tsx이므로 apps/web/content에 도달하려면 ../../../../../content로 5단계를 올라가야 했다. (main) 라우트 그룹 디렉토리를 빠뜨린 것이 원인이었다.

포스트 목록

/SEOJing/devLog/insight
파일 10개, 폴더 0개
엄청난 피드백생각보다 어려웠던 댓글, 완독 로컬스토리지디자인 시스템을 구축할 때 주의할 점폰트는 왜 메인 페이지에서만 적용이 안되고 있었을까?MDX DOM 트리 파싱하기MDX 관련 이슈 노트결국 Node.js 까지 와버렸다전체적인 플로우Storybook으로 디자인 시스템 테스팅하기MDX가 뭘까?