Next.js Git Blog (6): MDX 플러그인(rehype-pretty-code, shiki)

목차

소개#

앞서 MDX을 도입하면서 컴포넌트를 통한 확장성을 확보해보았다.
이를 적용하는 과정에서 추가로 확인한 내용이 있는데, MDX는 다양한 플러그인을 활용하여 마크다운의 본문을 더 다채롭게 꾸밀 수 있게 해준다고 한다.

이번 글에서는 코드 하이라이팅을 위한 rehype 라이브러리를 중심으로, 플러그인 설치 및 적용 과정을 정리해보고자 한다.


코드 블록 하이라이팅의 부재#

기본 마크다운 설정만으로는 운영 중인 블로그에서 몇 가지 아쉬운 점이 있었는데, 특히 개발 블로그에서 가장 중요한 코드 블록이 문제였다.

  • 단조로운 렌더링: 코드가 단순히 <pre>와 <code> 태그로만 감싸져 출력되어 시각적 구분이 어려웠다.
  • 수동 설정의 한계: CSS로 배경색이나 글꼴을 바꿀 순 있지만, 언어별로 다른 색상을 입히는 '하이라이팅'을 일일이 직접 구현하는 것은 불가능에 가까웠다.

그리고 이러한 문제의 해결을 MDX 플러그인을 통해 할 수 있었다.


MDX 플러그인의 두 축: Remark와 Rehype#

MDX는 처리 과정에 따라 크게 두 가지의 플러그인을 지원한다.

  • Remark: 마크다운 문법(Markdown AST)을 다룬다. (예: 표, 체크박스, 주석 추가 등)
  • Rehype: 변환된 HTML 구조(HTML AST)를 다룬다. (예: 코드 하이라이팅, 클래스 주입, ID 부여 등)

Next.js에서 마크다운을 파싱하는 serialize 함수의 mdxOptions를 통해 이 플러그인들을 주입할 수 있다.

// /utils/markdown-utils.ts
...
const mdxSource = await serialize(fileContents, {
	parseFrontmatter: true,
	mdxOptions: {
		format: 'mdx',
		remarkPlugins: [
            // 마크다운 문법 변환 플러그인
        ],
		rehypePlugins: [
            // HTML 결과물 가공 플러그인
        ],
	},
});

선택한 하이라이팅 플러그인#

많은 플러그인 중 나는 rehype-pretty-code & shiki 조합을 선택했다.

  • rehype-pretty-code
    코드 블록의 가독성을 극대화해 주는 플러그인
    단순히 색상만 입히는 것을 넘어 줄 번호 표시, 특정 줄 강조, 단어 강조 등 풍성한 기능을 제공
  • shiki
    내부에서 실제로 코드를 분석하는 엔진
    VS Code와 동일한 하이라이팅 엔진을 사용하여 매우 정확하고, 연결된 여러 스타일의 테마를 제공

📌 다른 플러그인은 없었을까?

처음에는 rehype-highlight와 highlight.js 조합을 먼저 적용했었다.
단순히 문법의 색상만 필요로 하여 기능적으로는 충분했었으나, 운영 과정에서 다음과 같은 불편함이 있었다.

  • 라이트/다크 모드 전환 시 CSS 파일의 별도 관리가 필요
  • 이미 정해진 패턴만 인식하여 스타일이 적용되지 않는 영역이 존재

플러그인 설치 및 적용#

가장 먼저 필요한 패키지를 설치하자. rehype-pretty-code와 하이라이팅 엔진인 shiki를 함께 설치해야 한다.

pnpm install rehype-pretty-code shiki

설치가 완료되었다면, 앞서 살펴본 mdxOptions에 플러그인을 추가한다. 단일 테마뿐만 아니라 라이트/다크 모드 테마를 동시에 가능하도록 설정할 수 있다.

// /utils/markdown-utils.ts
...
const mdxSource = await serialize(fileContents, {
	parseFrontmatter: true,
	mdxOptions: {
        ...
		rehypePlugins: [
            [rehypePrettyCode, {
                // theme: 'github-dark',
                theme: {
                    light: 'github-light',
                    dark: 'github-dark',
                },
            }],
		],
	},
});
 

위 예시에서는 가장 무난한 github-light와 github-dark를 사용했으나, shiki 공식 문서에 접속하면 다른 고품질 테마를 미리 볼 수 있으니 참고하자.

이후 취향에 맞는 테마명을 theme 속성에 적어주기만 하면 즉시 적용된다... 고 생각했지만 아니나 다를까 단번에 적용되진 않았다.😄


스타일 충돌: Shiki vs GitHub Markdown#

본문 영역의 전체적인 가이드라인을 잡기 위해 github-markdown-css 패키지를 사용하고 있었는데, 여기서 예상치 못한 문제가 발생했다. 이 패키지가 코드 블록(<pre>, <code>)에 대해 이미 강하게 스타일을 지정하고 있어, Shiki 테마가 적용된 코드 블록과 스타일 충돌이 발생했다.

이 문제를 해결하는데 생각한 방법은 아래와 같다.

  1. CSS 우선순위를 이용한 덮어쓰기 (선택)
    !important나 구체적인 셀렉터를 사용해야하지만, 외부 라이브러리를 건들지 않아도 되므로 유지보수에 유리하다고 생각했다.

  2. github-markdown-css 비활성화
    가장 깔끔하겠지만, 마크다운 전반의 스타일이 모두 바뀌게 되어 추가 작업이 필요했다.

나는 유지보수의 편의성을 고려하여 첫 번째 방법을 선택하고, 별도의 shiki.css 파일을 만들어 관리하기로 했다.

/* shiki.css */
 
/* 1. 라이트/다크 모드 변수 제어 */
:root {
	--shiki-dark: inherit;
}
 
@media (prefers-color-scheme: dark) {
	:root {
		--shiki-light: inherit;
	}
}
 
/* 2. Shiki 요소 스타일 (기존 라이브러리 스타일 덮어쓰기) */
pre {
	/* github-markdown-css의 배경색을 무시하기 위해 !important 사용 */
	background-color: var(--shiki-light-bg) !important;
	font-family: var(--font-fira-code), 'Roboto Mono', monospace;
	padding: 1.5rem;
	border-radius: 8px;
	overflow-x: auto;
}
 
pre code span {
	color: var(--shiki-light);
}
 
/* 3. 다크 모드 대응 */
@media (prefers-color-scheme: dark) {
	pre {
		background-color: var(--shiki-dark-bg) !important;
	}
 
	pre code span {
		color: var(--shiki-dark);
	}
}

마무리#

스타일 충돌을 해결한 뒤 다시 빌드해 보니, 이제서야 하이라이팅이 정상적으로 적용되는 것을 확인할 수 있었다.

CSS 우선순위를 활용해 기존 라이브러리의 스타일을 강제로 덮어쓰는 방식으로 문제를 해결했는데, 구현을 마치고 나니 라이브러리 의존성을 줄이면서도 더 깔끔하게 제어할 수 있는 '더 좋은 방법'은 없었을까 하는 고민이 되었다.

이 글에서 소개한 것 외에도 괜찮은 플러그인들이 많으니, 확인해보고 필요에 따라 잘 활용해보자.