MDX 블로그 이관과 렌더링 문제 해결
Confluence에 있던 기술 기록을 Next.js와 Nextra 기반 블로그로 옮겼습니다. 본문을 MDX로 만드는 것 외에도 제목과 파일명, 검색 결과, 다이어그램 크기처럼 실제로 읽을 때 드러나는 문제를 정리해야 했습니다.
글이 많아지면서 같은 내용을 다루는 문서가 겹쳤고, 검색에는 글 목록 페이지가 섞였습니다. 큰 그림은 본문 폭에 맞춰 줄이면 글자가 너무 작아졌습니다. 이 글에서는 이관 이후 읽기와 탐색을 위해 바꾼 부분을 정리합니다.
MDX 이관과 제목 정리
Confluence 페이지 ID가 들어간 파일명은 내용을 알 수 있는 이름으로 바꿨습니다. 제목 계층과 설명을 정리하고, 겹치는 명령어 문서는 합쳤습니다. 관련 글은 시리즈와 이전·다음 링크로 연결했습니다.
과거 프로젝트의 작업 날짜와 블로그로 옮긴 날짜도 구분했습니다. 이관한 날을 구현 시점으로 쓰면 오래된 기술과 당시 환경을 지금 선택한 것처럼 보일 수 있기 때문입니다.
검색과 공유 이미지 생성
정적 결과물에는 Pagefind 검색 인덱스를 만들었습니다. 태그와 게시글 목록 같은 탐색 페이지는 본문 검색에서 제외해 같은 글을 여러 경로로 찾는 문제를 줄였습니다. sitemap과 robots 설정, 빠진 description도 함께 정리했습니다.
공유 이미지 생성에는 한국어 폰트가 필요했습니다. 처음에는 외부에서 폰트를 가져왔지만 빌드 문제가 생겨 로컬 파일을 읽도록 바꿨습니다. 이미지 생성에 쓰는 폰트와 굵기를 프로젝트 안에서 관리하는 구성입니다.
| 기능 | 정리한 내용 |
|---|---|
| 검색 | 정적 결과물 인덱싱과 탐색 페이지 제외 |
| 공유 이미지 | 한국어 폰트 로컬 포함 |
| 글 탐색 | 제목·파일명·시리즈 연결 |
| 공개 결과물 | 소스 저장소와 분리 |
글 파일에서 정적 검색까지
지금 블로그에서는 글의 제목과 설명, 날짜, 초안 상태를 MDX 상단에서 관리합니다. 다음은 실제 작성 형식을 설명하는 예시입니다.
---
title: "설정 변경의 이유와 확인 과정"
description: "문제와 변경한 설정, 확인한 결과를 설명합니다."
date: 2026-10-18
draft: true
tags: ["example"]
---
## 실행 환경
본문과 코드 예시를 함께 작성합니다.draft: true는 편집 중이라는 표시입니다. 로컬에서는 내용을 읽을 수 있지만 배포용 페이지 처리와 검색·피드에서 제외하도록 했습니다. 검토용으로 여러 글을 모은 페이지에는 localOnly: true도 사용합니다. 이 값은 Nextra의 모든 사이트에서 자동으로 같은 의미를 갖는 옵션이 아니라 이 블로그의 페이지 생성 코드에서 처리하는 규칙입니다.
현재 소스에서는 app/[[...mdxPath]]/page.jsx가 로컬 전용 경로를 배포 대상에서 제외하고, 운영 환경의 초안 요청에는 본문을 반환하지 않습니다. sitemap·RSS·검색 대상도 같은 공개 기준을 사용해야 합니다. 메뉴에서만 숨기면 직접 주소나 검색 결과로 글을 발견할 수 있기 때문입니다.
빌드는 다음 순서입니다.
MDX·컴포넌트
→ Next.js 정적 결과물(out/)
→ 공개 글의 data-pagefind-body
→ Pagefind 인덱스(out/_pagefind)
→ 배포 저장소로 결과물 전달npm ci
npm run dev
# 로컬 글과 링크를 확인한 뒤, 별도 터미널에서 배포용 빌드 확인
npm run build이 프로젝트의 build 스크립트는 next build 뒤에 Pagefind를 실행합니다. MDX 파싱이 통과해도 인덱스에서 목록 페이지가 섞이거나 초안이 검색되는 문제는 남을 수 있습니다. 따라서 어떤 HTML에 검색 본문 속성을 붙이는지도 봅니다. 공유 이미지도 별도 생성 경로에서 로컬 폰트 파일을 읽으므로 글 본문 폰트가 정상이라고 이미지의 한국어까지 정상인 것은 아닙니다.
화면에 들어온 그림만 렌더링
Mermaid 컴포넌트에서는 IntersectionObserver로 화면에 들어온 시점을 기다립니다. 아래는 현재 app/_components/Mermaid.jsx에 있는 처리의 형태입니다.
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
observer.disconnect();
setIsIntersecting(true);
}
});
observer.observe(element);
// 컴포넌트를 해제할 때 observer.disconnect()로 정리합니다.element와 상태 갱신 함수는 컴포넌트에서 준비하는 값입니다. 이 부분만으로 Mermaid 그림을 만드는 전체 예제는 아니고, 렌더링을 시작할 조건을 보여주는 발췌입니다. 본문에 그림이 여러 개 있어도 아직 읽지 않은 아래쪽 그림을 모두 처음부터 그리지 않도록 했습니다.
그림이 나타난 뒤에는 SVG의 실제 크기와 확대 컨테이너의 크기가 맞아야 합니다. 지연 렌더링, 크기 계산, 확대·이동은 각각 다른 역할이어서 하나가 동작해도 나머지까지 해결됐다고 보지 않았습니다. 확인할 때는 글을 스크롤해 그림을 나타내고 확대·축소·초기화와 좁은 화면의 잘림을 함께 봅니다.
Mermaid 렌더링과 확대 기능
큰 Mermaid 그림은 본문 폭에 맞추면 읽기 어려웠습니다. 공통 컴포넌트에 확대와 이동 기능을 추가하고, 화면에 들어올 때 렌더링하도록 했습니다.
확대 기능을 붙인 뒤에도 SVG 크기와 글꼴, 초기 배율은 따로 조정해야 했습니다. 모든 그림에 같은 크기를 강제하면 작은 그림은 지나치게 커지고 큰 그림은 잘릴 수 있었습니다. 필요한 글에서는 초기 배율을 지정할 수 있게 했습니다.
Callout에서는 별도로 덮어쓴 스타일 때문에 잘림이 생겨 기본 스타일로 되돌렸습니다. 같은 페이지 안에서도 본문과 그림, 강조 상자는 서로 다른 크기 조건을 가져서 공통 스타일 하나로 처리하기 어려웠습니다.
소스와 공개 결과물 분리
소스는 비공개 저장소에서 관리하고 정적 결과물은 공개 배포 저장소에 두는 방식을 유지했습니다. 10월에는 공개 저장소 이름을 blog로 바꾸며 CI 대상과 화면 링크를 맞췄습니다. 블로그 도메인은 유지했습니다.
배포 확인에서는 공개 결과물이 올라갔는지와 Pages 원본 인증서가 준비됐는지도 나눠 봤습니다. 프록시를 거친 HTTPS 응답만으로 Pages 인증서 발급까지 완료됐다고 판단할 수는 없었습니다.
이관 결과는 MDX 파일을 만드는 데서 끝나지 않았습니다. 제목과 시리즈 링크를 정리하고, 본문 검색과 다이어그램 표시를 보완해 블로그에서 글을 찾아 읽을 수 있게 했습니다. 소스와 공개 결과물도 별도 저장소로 나눴습니다.
이후 글을 추가하거나 고칠 때도 목록·링크·검색·그림은 함께 확인해야 합니다. 렌더링 문제를 고친 것과 원문 내용의 검토가 끝난 것은 다르므로, 본문을 다듬는 작업은 계속 이어가고 있습니다.