HTML을 이미지 변환 및 다운로드 - 트러블 슈팅

HTML을 이미지로 변환하고 다운로드하는 기능을 구현하면서 예상하지 못한 여러 문제를 겪었다.
이 글은 그 과정에서 발생했던 이슈와, 문제를 해결하기 위해 시도했던 방법, 그리고 최종적으로 얻게 된 인사이트를 기록하기 위한 것이다.

간단한 작업인줄 알았지만 아니었다

처음에는 html2canvas 라이브러리를 통해 DOM을 canvas로 변환한 뒤 dataUrl을 생성하는것으로 간단하게 처리 할 수 있다고 생각했다. 하지만 실제 적용해보니 다운로드를 클릭하면 canvas 변환을 하는데 최소 2s ~ 2.5s 정도 많이 걸리면 3.5s 정도 이후 다운로드가 진행되니 UX 측면에서 좋지 않았다. 실제 팀 내 에서도 "개선할 수 있는 방법이 없을까?" 라는 의견이 있었다.

어떤 방법들을 시도했나?

1. 변환 & 다운로드 로직 분리

가장 먼저 시도한 것은 변환과 저장 로직을 분리 하는 작업을 진행하였다.
  • 컴포넌트가 마운트 완료되면 useEffect내 변환 로직 실행을 통해 이미지 dataUrl 미리 생성
  • 생성된 dataUrl옵션 변경 시 재활용할 수 있도록 memoization 적용
  • 다운로드 버튼 클릭 시에는 저장만 수행 이렇게 하니 다운로드 시점에서는 딜레이가 없었지만 변환에 걸리는 시간 자체는 줄어들지 않았다. 물론 memoization된 값이 재사용되는 경우에는 빠르게 동작했다.

2. 라이브러리 교체 (html2canvas -> modern-screenshot)

변환 타이밍을 바꿔도 근본적인 속도 차이는 없었다. 원인을 찾아보니 다음과 같은 이유였다.
  • DOM이 복잡할수록 렌더링 시간이 증가
  • 사용 중인 폰트, 이미지, 노드 수, CSS 복잡도 모두 성능에 영향 이러한 구조적인 한계 때문에 html2canvas는 시간이 걸릴 수밖에 없었다. 대안으로 modern-screenshot 라이브러리도 테스트해봤다. 이 라이브러리를 사용해본 이유는, 기존 html2canvas처럼 DOM을 수작업으로 canvas에 다시 그리는 방식이 아니라, 브라우저가 렌더링한 결과를 SVG, foreignObject, CSS Paint API 등을 활용해 더 가볍게 캡처할 수 있다는 점에서 더 나은 성능을 기대했기 때문이다.
  • 단순한 구조라면 html2canvas보다 빠르고 가볍게 동작
  • 렌더링이 끝난 후의 결과를 활용하니, 이론상 더 정확하게 캡처 가능
하지만 변환 방식이 달라도 복잡한 DOM 구조에 따른 성능 문제는 동일하게 존재했다.
결국 복잡한 DOM, 많은 리소스를 캡처할 때는 브라우저가 어떤 방식으로든 렌더링을 다시 계산해야 하기 때문에 큰 차이는 없었다.

3. 캡처 전용 폰트 적용 (Base64 사용)

추가로 시도한 것은 캡처 전용 폰트를 별도로 Base64로 임베딩하는 방식이었다.
  • 폰트를 Base64로 인라인 처리해 렌더링 시간을 단축
  • 이를 통해 렌더링 시간을 조금 개선할 수 있었다 이 방식은 번들 사이즈가 늘어나는 단점이 있었다. 이를 보완하기 위해 다음과 같은 추가 조치를 취했다.
  • 사용중인 font의 subset을 생성해 Base64로 변환 (latin 및 숫자&특수문자만 포함)
  • 실제 사용 중인 font-weight만 Base64 인라인 적용 이렇게 하여 불필요하게 번들 사이즈가 커지는 문제를 최소화할 수 있었다.

4. 저장 이후 일부 UI가 깨지는 현상

저장된 이미지에서 일부 UI가 깨지거나 누락되는 현상이 간헐적으로 발생했다. 이 문제는 일부 요소를 SVG로 교체하면서 해결할 수 있었다. 리서치를 해보니 modern-screenshot 사용 시 발생하는 이유는 다음과 같았다.
  • 렌더링 타이밍 문제modern-screenshot은 내부적으로 SVGforeignObject를 사용해 캡처를 수행하는데, 해당 시점에 브라우저가 완전히 렌더링하지 않은 요소가 있으면 누락되거나 깨져 보일 수 있다.
  • 폰트, 이미지 로드 상태 외부 폰트나 이미지가 완전히 로드되지 않은 상태에서 캡처가 실행되면, 기본 폰트로 대체되거나 이미지가 비어 보일 수 있다.
  • CSS 특성에 의한 한계foreignObject 기반 캡처 특성상 일부 CSS 효과 (filter, box-shadow, clip-path 등)가 정상적으로 반영되지 않는 경우가 있다.

정리

HTML을 이미지로 변환하고 다운로드하는 기능은 예상보다 훨씬 많은 변수에 영향을 받았다.
  • 복잡한 DOM 구조, 많은 리소스, 복잡한 CSS는 변환 속도에 직접적인 영향을 준다.
  • 폰트, 이미지 로드 상태에 따라 변환 결과가 달라질 수 있다.
  • 렌더링 타이밍에 민감해, 완전히 렌더링된 상태에서 캡처가 필요하다.
  • 중요한 요소는 SVG로 교체하면 안정적인 캡처가 가능하다.modern-screenshot은 구조가 단순할 경우 html2canvas보다 빠르게 동작하지만, 복잡한 구조에서는 근본적인 성능 차이는 크지 않았다. 결국 품질과 속도를 위해서는 DOM을 단순화하고, 캡처 전 상태를 철저히 준비하는 것이 중요하다는 점을 다시 한 번 느꼈다.

© 2025 dan.dev.log, All right reserved.

Built with NextJS