[ React: PDF Generator ] 맞춤형 PDF 렌더링 성능 최적화

2025. 11. 20. 21:50Dev Logs

✏️ 개요

사내에서 가장 오랫동안 유지보수/고도화를 맡고 있는 🔗창업교육 플랫폼 Scout에서 고도화 기능 추가 요청사항 중에서 사업계획서를 플랫폼 스타일의 커스텀 프레임으로 출력하고 이를 PDF로 변환하는 신규 기능이 추가되었습니다.


초기 개발 완료 보고 이후에도 고객사 측에서 콘텐츠가 프레임 내에서 잘리는 현상이 반복적으로 발생하였고, 기능 담당자가 마감 내 구현을 완료하지 못하면서, 제가 해당 기능을 분석 및 재구현하는 역할로 투입되었습니다.

 

❓ 문제와 원인 탐색

해당 문제가 수면 위로 올라오기 전까지는 타개발 업무를 진행하고 있어 몰랐지만, 대표님께서 신뢰하는(?) 저를 불러 코드 한번 분석해 보고 피드백 달라고 전달해 주셔서 자세하게 버그가 나는 케이스를 찾기 위해 가장 먼저 QA를 진행했습니다.

 

다양한 케이스로 QA를 해본 결과 단순하게 작성한 글만 제대로 정상적으로 생성되고 줄 바꿈이 연속으로 일어나거나 이미지를 여러 개 추가하게 되는 경우에 콘텐츠가 들어갈 여백이 남아도 페이지가 넘어가거나, 잘리거나, 페이지 자체가 스킵되는 심각한 문제가 있었습니다.

 

코드를 직접 확인해 보니 바이브 코딩만으로 구현한 듯 보였고 관련 파일이 약 5개였는데 하나의 파일당 약 7000줄의 코드로 작성되어 있어 분석이 매우 어려운 상황이었습니다. 그래도 반나절의 시간을 투자하여 문제를 파악했습니다.

 

추적결과, Layout단계에서 렌더링되지 않아 할당되지 않은 getComputedStyle메서드와 node에서 직접 할당된 style을 비교 계산하며 오차가 발생하고, 사이드 이팩트로 react의 auto batching 시스템까지 영향을 받아 발생하는 코너케이스로 확인되었습니다.

 

로직부분에서도 단순하고 직관적으로 풀 수 있는 프로세스임에도 불구하고,

최악의 경우 O(n³)의 복잡도를 만들어내는 로직이 불필요하고 변경에 유리하지 않다는 생각이 들어 개선하고자 했습니다.

결과적으로 2주의 시간이 넉넉한 시간이 아닌, 터무니없이 부족한 시간으로 반전되어 챌린지가 되는 순간이었습니다🥹

 

⛏️ 설계부터 다시

가장 먼저 본질적인 문제를 해결하기 위한 방향으로 설계하고자 했습니다.

다른 문제는 기본적으로 구현 난이도가 쉬웠으나, 커스텀 A4사이즈를 기반으로 json형태의 Text와 이미지를 잘림 없이 콘텐츠를 채우고, 페이지를 넘기는 기능을 구현하는 것이 생각보다 난이도 높았기 때문에 가장 우선 목표로 설계하였습니다.

 

 

🚨콘텐츠가 잘리거나 채우지 않고 넘어가는 문제

첫 번째로

가장 문제가 되는 부분은 바로 잘못된 대상을 기준으로 계산하는 문제였습니다.

단순히 초기 렌더링의 DOM을 기반으로 Layout 단계에서만 수치적 계산을 진행하고 실제 보이는 View와 미세한 규격 차이가 발생하여 제대로 된 페이지 넘김이나 생성이 어려운 문제가 있었습니다.

 

두 번째로

Text를 재귀하며 절삭하고 계산할 때 줄 바꿈에 대한 고려를 제대로 하지 못한 문제였습니다.

줄 바꿈을 절삭할 때 잘못된 로직으로 줄 바꿈이 절삭될 때 실제 요소에서 줄 바꿈의 높이만큼 취급되지 않거나, 단순히 공백으로 인식하는 문제가 있었습니다.

 

 

🔑 커스텀 PDF 프레임 규격 일치율 100%를 목표로

결과부터 말씀드리자면, 커스텀 PDF 프레임 규격 일치율 100%를 목표로 설계하고 개발하여 브라우저 View에서 보이는 수치를 정확히 반영하며 화면에 정상적으로 출력되는 PDF형 컴포넌트를 구현했습니다.


텍스트를 한 글자씩 계산/비교하며 오차와 리스크를 최소화하고, 콘텐츠 요소를 감싸는 wrapper의 height를 기준으로 내부 요소가 초과할 경우 남은 콘텐츠를 다음 페이지로 이어 재귀적으로 배치하도록 설계했습니다.


가상 measure 구조를 기반으로 카테고리별 콘텐츠를 점진적으로 커스텀 A4 프레임에 채우고, 모든 페이지 생성 완료 시 로직을 종료하며 각 페이지 우측 하단에 넘버링을 추가하여 차례대로 렌더링 되도록 구현했습니다.

 

✨ 정리하자면

  • 목표: 커스텀 PDF 프레임 규격 일치율 100%, 화면과 동일한 출력 보장
  • 접근 방법: 텍스트를 한 글자씩 계산/비교하며 오차와 리스크 최소화
  • 콘텐츠 Fit 처리:
    • 실제 콘텐츠를 감싸는 wrapper 요소의 height 기준
    • 초과 시 남은 콘텐츠를 다음 페이지로 이어 재귀적 배치
    • 가상 Measure 구조 활용: 정확한 위치와 크기 계산 기반
  • 페이지별 로직: 카테고리별 콘텐츠 점진적 배치 → 모든 페이지 생성 완료 시 로직 종료
  • 부가 기능: 각 페이지 우측 하단에 페이지 넘버링 추가
  • 결과: 안정적이고 규격에 맞는 PDF형 컴포넌트 렌더링 완성

 

 

🐢 완벽하지만, 느리다...

데드라인이 너무 가까워  "커스텀 PDF 프레임 규격 일치율 100%"를 최우선 목표로 정하고 안전성이 높은 방향으로 개발은 우선 완수했지만 안전성을 위해 구현했던 한 글자씩 넣으며 비교/계산하는 방식이 너무 많은 연산을 일으켜 Text 내용이 많아질수록 메인스레드에 부하가 커져 생성 완료까지의 시간이 너무 오래 걸린다는 문제가 있었습니다.

 

사실상 Text를 한 자씩 자르며 실제 요소에 넣어보고 비교하는 방식으로 생성하다 보니 어찌 보면 당연한 이슈였으며 극단적인 케이스로 얼마나 비효율적인 시스템인지 성능을 확인해 볼 필요가 있었습니다.

 

그 예시로 Text 약 2만자와 이미지 4개 기준의 81페이지의 사업계획서를 작성하여 테스트해 보았고 약 18초 정도 소요되는 것을 확인하였고, 사용이 불가능한 수준이라고 판단되어 예정되어 있던 점진적 리팩터링을 시작했습니다.

 

 

💡 단순하면서도 본질적인 해법은?

가장 먼저 떠올린 것은 "이진 탐색 알고리즘"이었습니다.

한 글자씩 비교하는 본질적인 문제를 개선해야 하기 때문에 한 글자씩 탐색하는 것이 아닌, 최적의 해를 만들기 위해선 이진 탐색을 활용하면 좋겠다! 라고 생각했습니다.

 

다음으로 떠올린 방법은 raf( request animation frame )을 활용하는 것이었습니다.

 

 

🐇 신속한 아이디어 검증

이론적으로 확신을 가지고 떠올린 이진 탐색 알고리즘 반영은 콘텐츠 연산 관련 코드를 전체적으로 변경해야 하기 때문에 비교적 규모가 큰 반면,  raf를 적용하는 것은 비교적 간단하고 빠르게 변경/적용할 수 있어 우선 검증을 해보기로 했습니다.

 

결과적으로 생성까지 약 40초 이상으로 정도로 오히려 더 느려졌지만, 로딩스피너의 렌더링은 최적화되었으며 브라우저 자체 부하는 덜어진 것을 확인할 수 있었습니다.

 

두 번째로 떠올린 raf의 활용은 저의 오판이었습니다.

화면(렌더링) 최적화와 실질적인 연산의 최적화는 개념 자체가 다르다는 것을 순간 망각했었던 것 같습니다. 그렇게 검증까지 거친 후에서야 스스로 너무 안일했다는 생각이 들었습니다🥹...

 

raf는 렌더링 타이밍(fps)에 맞춰 매서드 등을 실행하기 위함이지, 단시간에 연산을 최적화하기 위해서가 아니라는 것을 다시금 마음에 새기는 계기가 되었습니다.

 

 

🚀 이진 탐색을 활용한 최적화, 그 결과는?

우선 수치화된 지표가 필요하여 측정을 위한 performance hook을 만들기로 했습니다.

 

간단히 구현한 performance 매서드를 활용한 측정 타이머 훅

 

 

위처럼 구현한 훅을 활용하여 먼저 이진 탐색 적용 전의 컴포넌트에서 PDF 규격 컴포넌트를 모두 생성하는데 얼마나 걸리는지 측정해 보니 아래와 같이 이전에 직접 세어본 18초와 유사한 18.7초 정도로 측정되었습니다.


알고리즘 적용 전 PDF 규격 컴포넌트 생성 완료 시간


 

 

다음으로 이진 탐색을 적용한 컴포넌트에서 생성 속도는 아래와 같이 1초가 채 되지 않는0.6초가 소요되었습니다.


이진 탐색 알고리즘을 활용한 PDF 규격 컴포넌트 생성 완료 시간


 

 

🚩결과

가장 큰 성과는 아무래도 "이진 탐색 알고리즘을 활용한 PDF 규격 컴포넌트 생성 완료 시간 단축" 입니다.

 

[ Text 약 2만자 + 이미지 4개 기준 ]

생성 완료 시간 [ 18784.40ms 633.80ms ] - 2,863% 향상( 약 96.6% 시간 단축 )

 

✍🏻마치며...

알고리즘의 활용은 효율면에서 극단적인 차이를 보여 줄 수 있다. 라는 결과를 직접 적용하여 확인할 수 있는 경험이었고 어떤 상황에서 어떠한 알고리즘을 활용할 수 있는지가 매우 중요하다는 생각이 들었습니다.

 

직접 특정 알고리즘을 구현 할 수 있는 능력도 중요하다고 생각하지만, AI가 상용화되고 발전한 지금에서는

"어떠한 상황에서 어떤 알고리즘을 활용하면 더 효율성을 높일 수 있겠구나."하며

알고리즘 활용 아이디어를 떠 올리는 능력 또한 중요해진 것 같습니다.

 

그 만큼 단순 코딩 능력보다는

"확장된 사고력을 유연한 창의력과 함께 문제를 해결하고, 더 좋은 성능을 구현하여 사용자에게 제공할 수 있는 능력을 가진 개발자가 미래에는 더 인정받는 좋은 개발자이지 않을까?"

하는 생각이 들어 앞으로도 꾸준히 딥다이브 하고자 하는 마음이 굳혀지는 계기가 되었습니다.