개발 slecs

HTML을 Markdown 파서에 이중으로 통과시키던 블로그 렌더링 버그

목차

들여쓰기가 들어간 HTML 콘텐츠가 Markdown 파서를 한 번 더 통과하면서 <code> 블록으로 뒤집히는 버그를 잡았다.


배경 — "이미 HTML인데 왜 또 파싱하지?"

블로그 포스트 렌더링 경로를 건드리다가 이상한 걸 발견했다. 글 본문 일부가 화면에서 <pre><code> 블록으로 뭉텅이 잡혀서 나오는데, 정작 원본 콘텐츠엔 코드 블록이 없었다. 첫 번째 의심은 당연히 CSS 였다. pre 스타일 오버라이드 문제인가 싶어서 DevTools 를 한참 뒤졌는데 스타일은 멀쩡했다.

두 번째 의심으로 넘어가서 렌더링 파이프라인을 역으로 추적하기 시작했다. [...slug].astro 를 열어 보니 contentmarked.parse() 에 넣고 있었다. 문제는 그 content 자체가 이미 HTML 문자열이라는 거다. 즉 HTML → Markdown 파서 → HTML 이라는 이중 변환이 일어나고 있었던 것.

Markdown 파서 입장에서는 들여쓰기 4칸 이상의 텍스트를 코드 블록으로 해석하는 게 스펙상 올바른 동작이다. 그런데 HTML 을 받아서 처리하면 들여쓰기로 포맷된 태그 안쪽 텍스트가 죄다 <pre><code> 로 변환되고, 원래 있던 <p> 태그의 열고 닫힘 쌍도 파서가 임의로 재해석하면서 깨진다. 결과적으로 DOM 이 예측 불가능한 형태로 나왔다.


어디서 이런 일이 생기나

이 류의 버그는 대체로 데이터 소스 교체 시점에 발생한다. 처음 구현할 때는 content 가 Markdown 원문이었을 수 있다. 그 흐름에서는 marked.parse() 가 완전히 맞는 처리다. 이후 CMS 나 파이프라인을 바꾸면서 content 가 이미 렌더링된 HTML 로 내려오게 됐는데, 파싱 코드를 건드리지 않은 채로 남아 있었던 것. 코드 리뷰에서 잡을 수 있었던 부분이지만, 타입 정보가 string 하나로 퉁쳐져 있으면 IDE 도 경고를 못 준다.

비슷한 패턴으로 흔히 겪는 케이스들:

  • API 응답이 plain text 에서 HTML 로 바뀌었는데 프론트 렌더러는 그대로 escape 처리
  • 에디터에서 저장할 때 이미 sanitize 된 HTML 을 불러와서 다시 sanitize → 특수문자 이중 인코딩
  • SSG 빌드 타임에 변환된 콘텐츠를 런타임에 또 변환

공통점은 "이 시점에서 content 의 포맷이 뭔지" 를 암묵적으로 가정하는 코드가 남아 있다는 거다.


수정 내용

변경은 단순하다. [...slug].astro 에서 marked.parse(content) 호출을 제거하고, content 를 그대로 set:html 에 내려줬다.

<!-- Before -->
<article set:html={marked.parse(content)} />

<!-- After -->
<article set:html={content} />
항목 변경 전 변경 후
입력 HTML 문자열 HTML 문자열
처리 marked.parse() 통과 그대로 바인딩
들여쓰기 해석 code block 으로 변환 원본 유지
<p> 파서가 임의 재구성 원본 그대로

파일 한 줄 바꿨지만 영향 범위는 전체 포스트 페이지다. 배포 후 문제가 됐던 글들을 직접 확인해서 구조가 정상 복구된 걸 눈으로 검증했다.


회고

사실 이 코드가 왜 저기 있었는지 처음엔 맥락을 몰랐다. 삭제하기 전에 git blame 으로 해당 라인 커밋 히스토리를 확인했고, 도입 당시 content 포맷이 달랐다는 걸 그제야 파악했다. "왜 있는지 모르는 코드"를 섣불리 지우는 건 리스크다. 반드시 맥락을 확인하고 나서 결정해야 한다.

콘텐츠 파이프라인을 바꿀 때는 소비 측 코드가 어떤 포맷을 기대하는지 명시적으로 문서화하거나, 타입 레벨에서 구분해 두는 게 낫다. string 대신 HtmlString 같은 branded type 을 두면 파서를 실수로 끼워 넣는 상황 자체를 막을 수 있다. 규모가 커지면 그런 약속들이 코드 안에 박혀 있어야 나중에 맥락 없이 합류한 사람도 실수를 방지할 수 있다.

끝.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.