마크다운으로 쓰는 기술 블로그에 리액트 컴포넌트를 끼워 넣는 방법을 unified 변환 파이프라인부터 파고든 글이다. 마크다운 → mdast → hast → HTML 각 단계를 코드로 직접 돌려 확인한 뒤, Gatsby 환경에서 이미 만들어진 HTML 을 다시 분해해 컴포넌트로 치환하는 경로를 택한다.
핵심 포인트- 마크다운은 raw HTML 태그를 지원하므로 MD 안에 HTML 을 써서 도형 정도는 그릴 수 있지만, 컴포넌트를 넣으면 훨씬 편해진다.
- Gatsby·MDX·Docusaurus·Next.js 예제까지 내부적으로 unified 를 쓴다. unified 는 텍스트의 문법을 이해해 Syntax Tree 로 바꾸고 포맷을 변환한다.
- mdast 는 마크다운의 구문 트리, hast 는 HTML 의 구문 트리다. 트리로 바꿔 두면 emphasis 타입 노드만 찾아 처리하는 식으로 문법 적용이 쉬워진다.
- Gatsby 는 변환 과정에 개입하기 어렵다. 플러그인이 모든 옵션을 열어 두지 않아 오히려 수정이 힘들고, 원본 마크다운을 직접 받으면 설치한 플러그인이 무용지물이 된다.
- 그래서 최종 HTML 을 `rehype-parse` 로 hast 로 되돌리고 `rehype-react` 의 components 옵션으로 특정 태그를 리액트 컴포넌트에 연결한다.
상세 정리- 동기: 블로그를 쓰다 보니 마크다운 기본 문법만으로는 독자에게 효과적으로 전달하기 어려운 표현이 생겨 컴포넌트 삽입을 알아보게 됐다.
- 예시로 든 것들: 임의 태그를 특정 컴포넌트로 치환하기, 마우스 오버 시 iframe 으로 페이지를 미리 보여주는 컴포넌트 등이다.
- 변환 1단계: `remark-parse` 로 마크다운을 mdast 로 바꾸면 텍스트 영역과 emphasis 노드가 트리 구조로 나뉜 JSON 을 확인할 수 있다.
- 변환 2단계: `remark-rehype` 로 mdast 를 hast 로 바꾸면 emphasis 노드가 tagName 을 가진 element 노드로 바뀐다. HTML 구조로 넘어왔다는 신호다.
- 변환 3단계: hast 를 다시 텍스트 HTML 로 조립한다. 세 단계를 그림 한 장으로 요약해 두었다.
- 이 흐름을 이해하면 어디에 손대야 문법을 추가하거나 가공할 수 있는지가 보인다는 것이 글의 중간 결론이다.
- 확장 방법 두 가지를 소개한다. 마크다운을 mdast 로 바꾸는 단계에서 커스텀 문법을 직접 넣거나, 아직 제안 단계지만 널리 지원되는 Generic directives(`:::커스텀-효과 ... :::`)로 노드에 메타 정보를 붙이는 방법이다. 후자는 micromark 가 권장하는 확장 방식 중 하나다.
- Gatsby 의 제약: page 쿼리의 `markdownRemark` 는 플러그인이 이미 적용된 HTML 을 준다. `rawMarkdownBody` 로 원본을 받을 수는 있으나 그러면 플러그인 효과를 전부 잃는다.
- 채택한 우회: 최종 결과물에 가까운 지점만 손댄다. `rehype-parse` 로 HTML 을 hast 로 만들고, `rehype-react` 로 리액트 노드로 변환한다.
- 치환 원리: HTML 에 커스텀 태그를 쓰면 해당 노드의 tag 가 그 이름으로 잡히고, `rehype-react` 의 components 옵션이 태그명과 일치하는 노드를 지정한 리액트 컴포넌트로 바꾼다.
- 대안 언급: `html-react-parser` 류 패키지로도 같은 결과를 얻을 수 있지만 unified 를 살펴본 김에 플러그인으로 처리했다고 밝힌다.
- MDX 소개: 확장자를 `.mdx` 로 하면 마크다운에서 JSX 를 그대로 쓸 수 있다. 원리는 MDX 내용을 자바스크립트로 컴파일해 런타임에 실행하는 것이라 React 에 종속되지 않고 Preact·Vue·Emotion 과도 쓸 수 있다.
- Next.js·Gatsby 에 MDX 를 붙이려면 각 플러그인을 설치하면 된다고 안내하며 마무리한다.
왜 읽나정적 블로그에 인터랙션을 넣고 싶은 프론트엔드 개발자에게 마크다운 파이프라인의 어느 지점을 잡아야 하는지 단계별로 보여준다.