
코드 블록에 flowchart LR이라고 쓰면 다이어그램이 그려지는 블로그를 만들고 싶었다.
SEO 기본 인프라(sitemap, metadata)를 구축한 후, 콘텐츠 자체의 품질을 높이는 작업을 했다. Mermaid 다이어그램 지원, FAQ 구조화 데이터, OG 이미지 치수 명시, Favicon, Cloudflare CDN 캐시. 각각은 30분~1시간짜리 작업이었는데, 하루에 몰아서 처리했다.
기술 블로그에서 아키텍처나 플로우를 설명할 때 다이어그램이 필요하다. 이미지로 만들면 수정이 번거롭고, 텍스트로 설명하면 한눈에 안 들어온다. Mermaid는 마크다운처럼 텍스트로 다이어그램을 정의하고 SVG로 렌더링해주는 라이브러리다.
구현 아이디어는 간단했다. PayloadCMS의 Lexical 에디터에서 코드 블록의 언어를 mermaid로 설정하면, 프론트엔드에서 이를 감지하여 MermaidDiagram 컴포넌트로 렌더링한다.
MermaidDiagram 컴포넌트는 클라이언트 전용이다. mermaid 패키지가 꽤 크기 때문에 동적 import로 코드 스플리팅했고, SSR에서는 렌더링하지 않는다. 에러가 발생하면 원본 코드 블록을 폴백으로 보여준다.
// MermaidDiagram.tsx
'use client'
export function MermaidDiagram({ chart }: { chart: string }) {
useEffect(() => {
async function render() {
const mermaid = (await import('mermaid')).default
mermaid.initialize({ startOnLoad: false, theme: 'neutral' })
const { svg } = await mermaid.render(id, cleaned)
container.innerHTML = svg
}
render()
}, [chart])
}삽질 포인트가 하나 있었다. Lexical 코드 블록에서 텍스트를 추출할 때, 텍스트 노드 사이에 linebreak 타입 노드가 끼어 있다. 이걸 무시하고 텍스트만 이어 붙이면 줄바꿈이 사라져서 Mermaid 구문 에러가 발생한다. linebreak 노드를 \n으로 변환해줘야 한다.
Posts 컬렉션에 faq 배열 필드를 추가했다. question/answer 쌍을 입력하면, PostDetail 페이지에서 두 가지 일이 일어난다.
// FAQ JSON-LD 자동 생성
{post.faq?.length > 0 && (
<script type="application/ld+json">
{JSON.stringify({
"@type": "FAQPage",
mainEntity: post.faq.map(item => ({
"@type": "Question",
name: item.question,
acceptedAnswer: { "@type": "Answer", text: item.answer }
}))
})}
</script>
)}Google 검색 결과에서 FAQ 리치 스니펫으로 표시되면 클릭률이 올라간다. 반드시 표시되는 건 아니지만, 구조화 데이터를 심어두면 기회는 생긴다.
나머지 세 가지는 빠르게 정리한다.
SNS에서 링크를 공유할 때 이미지가 제대로 표시되려면 og:image:width=1200과 og:image:height=630을 명시해야 한다. PayloadCMS의 sizes.hero 이미지 URL을 OG 태그에 사용하도록 설정했다.
favicon.ico와 apple-icon.png를 추가했다. 여기서 삽질이 있었는데, Turbopack 빌드가 favicon의 색공간을 검사해서 RGBA가 아닌 RGB 포맷이면 빌드 에러가 발생한다. 이미지 변환 도구에서 RGBA를 명시해야 한다.
Next.js의 minimumCacheTTL: 2592000 (30일)을 설정하여 Cloudflare CDN이 이미지를 캐시하도록 했다. 이미지 최적화 요청이 원본 서버까지 가지 않으니 응답 속도가 빨라진다.
다섯 가지 개선 중 어느 하나도 대단한 건 아니다. Mermaid 통합은 serialize 레이어에서 language를 감지하는 한 줄 분기문이 핵심이고, FAQ 스키마는 JSON-LD 스크립트 태그 하나, OG 이미지는 메타 태그 두 개, Favicon은 파일 하나, CDN 캐시는 설정값 하나다.
하지만 이런 작은 것들이 모이면 차이가 난다. 다이어그램이 있는 글은 코드만 있는 글보다 이해가 빠르고, FAQ 리치 스니펫은 검색 결과에서 면적을 넓혀주고, OG 이미지가 제대로 뜨는 링크는 공유할 때 클릭률이 높다.
Lexical 에디터의 linebreak 노드 처리가 가장 시간을 잡아먹었다. 눈에 보이지 않는 노드 타입이 있다는 걸 알기까지가 오래 걸렸고, 한번 알고 나니 수정은 금방이었다. "보이지 않는 것"을 의심하는 습관이 디버깅의 핵심이다.
Lexical 에디터의 코드 블록에서 언어를 mermaid로 설정하고 다이어그램 구문을 작성한다. serialize-lexical.tsx에서 code 노드의 language가 mermaid인 경우를 감지하여 MermaidDiagram 클라이언트 컴포넌트로 렌더링한다. mermaid 패키지는 동적 import로 코드 스플리팅한다.
FAQPage JSON-LD 스키마를 추가하면 Google 검색 결과에서 FAQ 리치 스니펫으로 표시될 수 있다. 검색 결과 페이지에서 차지하는 면적이 넓어지고, 클릭률이 올라간다. 다만 Google이 반드시 리치 스니펫을 표시하는 것은 아니다.
Turbopack 빌드 과정에서 favicon.ico의 색공간을 검사한다. RGB 포맷의 favicon은 빌드 에러를 발생시키고, RGBA 포맷이어야 정상적으로 처리된다. ImageMagick 등으로 변환할 때 RGBA를 명시해야 한다.