왜 SuperDoc이 마지막 이미지만 표시하는가: DOCX 내부 구조 조사

발행: (2026년 3월 3일 오전 01:20 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

증상

생성된 보고서에 여러 개의 속성 이미지가 그리드 형태로 배열되어 있었습니다.

편집기 내부 예상SuperDoc 내부 실제 결과
이미지 1 → 슬롯 1슬롯 1 → 마지막 이미지
이미지 2 → 슬롯 2슬롯 2 → 마지막 이미지
이미지 3 → 슬롯 3슬롯 3 → 마지막 이미지

이상하게도:

  • Microsoft Word → 정상
  • LibreOffice → 정상
  • Google Docs → 정상
  • SuperDoc 편집기 → 오류

따라서 DOCX 자체는 유효했으며, 문제는 편집기별로 나타났습니다.

환경

도구버전
SuperDoc1.16.x
docx-templates4.15.0
Node20.x

이미지는 docx-templates 루프를 사용하여 삽입되었으며 docxUrl을 통해 편집기에 로드되었습니다.

첫 번째 확인: DOCX가 손상되었나요?

Initial assumption: the DOCX generation might be broken.

Verification steps

  1. Opened in Word – ✅
  2. Opened in LibreOffice – ✅
  3. Converted to PDF – ✅
  4. Uploaded to Google Docs – ✅

Everything rendered correctly everywhere except inside the editor.
Conclusion: basic DOCX corruption ruled out.

Source:

Deep‑Debugging Approach

DOCX 파일은 단순히 ZIP 아카이브이기 때문에, 원시 XML을 추출하여 검사했습니다:

word/document.xml
word/_rels/document.xml.rels
word/media/

목표는 이미지가 내부적으로 어떻게 참조되는지 확인하는 것이었습니다.

Finding 1: Non‑Standard Relationship IDs

보통 DOCX는 rId1, rId2, rId3 같은 관계 ID를 사용합니다.
생성된 DOCX에는 다음과 같은 해시 기반 ID가 포함되어 있었습니다:

수행한 테스트 – 모든 관계 ID를 표준 rId1, rId2, … 로 변환하고 참조를 업데이트했습니다.

결과: 편집기 동작에 변화가 없었으며, 모든 이미지 슬롯이 여전히 마지막 이미지를 표시했습니다.
결론: 관계 ID가 주요 원인은 아니었습니다.

Finding 2: Media Filenames

생성된 파일 이름은 다음과 같은 형태였습니다:

template_document.xml_img2073076884.jpg

일반적인 image1.jpg, image2.jpg 형태가 아니라는 점이었습니다.

수행한 테스트 – 파일 이름을 표준 형식으로 바꾸고 참조를 업데이트했습니다.

결과: 편집기에서 여전히 문제가 발생했습니다.

결론: 파일 이름 규칙이 원인이 아니었습니다.

Finding 3: Images Inside Tables?

이미지가 표 셀 안에 들어가면서 평탄화되거나 병합되는 것이 의심되었습니다.

점검: 원시 XML 구조 – 이미지가 이미 독립적인 단락에 있었으며, 복잡한 표 그리기 구조 안에 중첩되어 있지 않았습니다.

결과: 구조적인 문제는 발견되지 않았습니다.

Finding 4: Base64 External References

편집기 내부에서 미디어 파일 해석 문제를 배제하기 위해 이미지를 base64‑인코딩된 외부 참조로 변환했습니다.

결과: 여전히 문제가 지속되었으며, 모든 슬롯이 마지막 이미지를 계속 표시했습니다.

결론: 문제는 이미지가 저장되거나 참조되는 방식이 아니라, 편집기가 drawing‑node의 정체성을 해석하는 방식에 있었습니다.

지금까지 가장 강력한 단서 (미확인)

각 이미지의 drawing XML을 검사하던 중, 일정한 패턴이 드러났습니다:

모든 이미지에 id="0"이 들어 있었습니다.

OOXML 사양에 따르면, 이 속성은 문서 내 그림 객체당 고유해야 합니다.
docx-templates가 생성된 모든 이미지에 id="0"을 할당하는 것으로 보입니다.

작동 가설: SuperDoc은 렌더링에 ProseMirror(또는 유사 엔진)를 사용합니다. 파서가 pic:cNvPr/@id를 사용해 drawing 노드를 구분한다면, 중복된 ID 때문에 모든 이미지를 동일한 노드로 인식하고 마지막에 로드된 이미지가 각 슬롯을 “점령”할 수 있습니다.

참고: 이는 가설일 뿐이며, 아직 확인된 해결책은 아닙니다. 다음 단계는 docx-templates 출력에 고유한 순차 ID를 삽입하도록 패치하고, 서버를 깨끗이 재시작한 뒤 결과를 검증하는 것입니다. 이전에 실패한 테스트는 서버를 재빌드 후 재시작하지 않아 오래된 컴파일 코드를 대상으로 했던 것이 원인이었습니다.

지금까지 시도한 내용

시도결과
표에서 이미지 추출변화 없음
관계 ID를 rId1/rId2 로 이름 변경변화 없음
미디어 파일 이름을 image1.jpg 로 변경변화 없음
이미지를 base64 외부 참조로 변환변화 없음
드로잉 XML 검사모든 이미지에서 중복 id="0" 발견
고유 ID로 패치아직 완전히 검증되지 않음

현재 상태

문제는 아직 조사 중입니다.

주요 가설: 모든 이미지에서 pic:cNvPr/@id="0"이 중복되어 편집기가 이를 하나의 노드로 병합합니다.

다음 단계

  1. drawing XML에 고유 ID를 삽입합니다.
  2. 서버를 완전히 재시작합니다.
  3. 깨끗한 검증을 수행합니다 (DOCX를 SuperDoc, Word, LibreOffice, Google Docs에서 열어 확인).

디버깅 세션에서 얻은 교훈

  1. Word에서 동작한다 해도 여전히 잘못될 수 있다
    Word는 많은 OOXML 위반을 조용히 허용한다. 편집기와 변환기는 더 엄격하다. Word 렌더링이 올바름과 동일하다고 가정하지 말라.

  2. DOCX를 내부적으로 항상 검사하라
    DOCX를 ZIP 파일로 취급한다. 압축을 풀고 XML을 검사하고 패턴을 검색하며 구조를 확인하면 디버깅이 훨씬 빨라진다.

  3. 렌더링 엔진은 다르게 동작한다

ToolStrictness
Word매우 관대함
Google Docs보통
LibreOffice엄격함
Editor engines매우 엄격함

편집기별 버그는 종종 사양이 요구하지만 Word가 조용히 무시하는 구조적 가정에서 비롯된다.

왜 이걸 문서화하는가

버그는 아직 해결되지 않았지만 디버깅 과정에서 가능한 근본 원인(중복 pic:cNvPr/@id)을 발견했습니다. 이 과정을 공유하면 비슷한 편집기‑특정 렌더링 문제를 겪는 사람들에게 도움이 될 수 있고, “워드에서 보기엔 괜찮다”는 수준을 넘어 DOCX 출력물을 검증해야 한다는 점을 상기시켜 줍니다.

DOCX 이미지 렌더링에서 비슷한 현상을 겪으셨다면, 어떤 결과를 얻었는지 알려주시면 정말 감사하겠습니다.

0 조회
Back to Blog

관련 글

더 보기 »

실제로 이해가 되는 Java 배열 메서드

Arrays는 솔직히 처음에 혼란스러웠다. 개념 자체는 — 여러 값을 저장한다는 것, 괜찮다. 하지만 서로 다른 클래스에 흩어져 있는 모든 utility methods? 그게 …