MDX에서 커스텀 컴포넌트 자동 관리하기

August 18, 2025

2 min read

MDX의 가장 큰 장점은 내가 만든 React 컴포넌트를 자유롭게 문서 안에서 활용할 수 있다는 점이다.

예를 들어 작성했던 CSS Flex 기본 알기 글을 보면, 중간중간 인터랙티브한 요소들이 들어가 있다. 이러한 요소들은 모두 MDX에서 불러와 사용하는 커스텀 컴포넌트다.

기존 방식: mdx.tsx에서 직접 관리

처음에는 mdx.tsx 파일 안에서 사용할 컴포넌트를 직접 import/export 하며 관리했다.

let components = {
  Image: RoundedImage,
  Link: CustomLink,
  code: Code,
  Table,
  ImageWithCaption,
  Highlight,
  ...mdxComponents,
};

export function CustomMDX(props) {
  return (
    <MDXRemote
      {...props}
      components={{ ...components, ...(props.components || {}) }}
    />
  );
}

여기서 MDXRemote는 next-mdx-remote 라이브러리에서 제공하는 컴포넌트로, 서버에서 직렬화된 MDX 콘텐츠를 클라이언트에서 렌더링할 수 있게 해준다.

기존 방식과 달라진 점은 ...mdxComponents 부분이다. 이제는 mdx.tsx 안에서 하나하나 import 하지 않고, 외부에서 자동으로 생성된 mdxComponents 객체를 불러와 관리한다.

mdxComponents의 구조

예를 들어 mdxComponents 객체는 다음과 같이 생성된다.

export const mdxComponents = {
  FlexFlexGrow,
  FlexFlexShort,
  FlexFlexShrink,
  FlexJustify,
  FlexOrder,
  FlexAlignContent
};

자동화 스크립트: generateMdxIndex

mdxComponents는 자동화 스크립트를 통해 모든 컴포넌트를 탐색해 index.ts 파일을 생성하는 방식으로 채워진다.

function toPascalCase(str: string) {
  return str
    .replace(/[-_/](.)/g, (_, c) => c.toUpperCase())
    .replace(/^(.)/, (c) => c.toUpperCase());
}

async function generateMdxIndex() {
  const componentsDir = path.resolve(
    process.cwd(),
    "app/components/mdxComponents"
  );
  const indexFilePath = path.join(componentsDir, "index.ts");

  // 하위 폴더까지 탐색
  function getAllComponentFiles(dir: string): string[] { ... }

  const files = getAllComponentFiles(componentsDir);

  // index.ts 재생성
  if (fs.existsSync(indexFilePath)) fs.unlinkSync(indexFilePath);

  // import/export 구문 생성
  const imports = files.map(...).join("\n");
  const exports = `export const mdxComponents = { ... };`;

  fs.writeFileSync(indexFilePath, `${imports}\n\n${exports}\n`);
}

이 스크립트는 app/components/mdxComponents 폴더 안에 있는 모든 .tsx/.jsx 파일을 탐색해 자동으로 index.ts를 만든다. 또한 toPascalCase 함수를 사용해 파일명을 PascalCase로 변환한다.

폴더 구조가 다음과 같다고 하자.

app/components/mdxComponents/
 ├─ Button.tsx
 └─ Input.tsx

자동으로 생성되는 index.ts는 아래와 같다.

export const mdxComponents = {
  Button,
  Input,
};

실행 방법

빌드하기 전에 항상 이 스크립트를 실행해야 하므로, package.json에 다음과 같이 추가한다.

"scripts": {
  "generate-mdx-components": "tsx scripts/generate-mdx-components.ts",
  "dev": "pnpm run generate-mdx-components && next dev",
  "build": "pnpm run generate-mdx-components && next build"
}


정리

기존 Ruby 기반의 Jekyll 블로그에서 React 기반 Next.js + MDX 블로그로 옮긴 뒤, 가장 큰 장점은 바로 이 부분이 아닐까 싶다.

스크립트를 통해 자동화를 적용함으로써 mdx.tsx에서 컴포넌트를 직접 관리하는 수고를 줄일 수 있고, 동시에 유지보수성까지 크게 높일 수 있다.