TL;DR

  • react-pdf로 수십 페이지를 넘는 PDF를 브라우저에서 생성하면 동기식 계산이 메인 스레드를 점유해 탭이 멈출 수 있으며, 웹 워커로 옮기면 문서 생성 중에도 인터페이스가 반응함.
  • PDF 생성은 스타일 해석, 글리프 변환, 문단 줄 나누기, 페이지 나누기를 수행하는 긴 계산 작업임.
  • 브라우저의 메인 스레드는 화면 그리기, 스크롤, 클릭 처리도 담당하므로 PDF 계산이 진행되는 동안 이러한 작업이 모두 멈춤.
  • React 요소는 구조화 복제 방식인 postMessage로 전달할 수 없으므로 문서 컴포넌트는 워커 안에 두고 일반 데이터만 전달해야 함.
  • 작은 문서에는 워커 설정의 빌드 단계와 메시지 프로토콜이 불필요한 부담이므로, 실제 멈춤 현상을 측정한 뒤 적용하는 방식임.

문제가 발생하는 이유

  • 가끔 react-pdf가 개발 환경에서는 잘 작동하지만, 고객이 200페이지 분량의 보고서를 다운로드하는 순간 탭이 멈춘다는 문제 제기가 나옴. 경우에 따라 Chrome이 페이지 종료를 제안하며, 청구서와 관련해 사용자에게 이런 선택을 요구하는 것은 우려스러운 상황임.
  • 기능이 고장 난 것이 아니라, 요청한 작업이 잘못된 스레드에서 실행되는 상황임.
  • PDF 생성은 대기 중에 양보하는 입출력(I/O) 작업이 아니라, 스타일 해석, 문자를 글리프로 변환, 모든 문단의 줄 나누기, 페이지 끝 결정 등을 연속해서 수행하는 계산 작업임.
  • 각 단계는 react-pdf를 호출한 스레드에서 동기식으로 실행됨. Node.js에서는 문제가 없지만, 브라우저에서는 화면 그리기와 스크롤, 클릭 응답을 담당하는 메인 스레드가 작업을 수행함.
  • 문서 계산이 진행되는 동안 메인 스레드의 다른 작업은 모두 멈춤. 페이지가 몇 장뿐이면 알아차리기 어렵지만, 작업량은 콘텐츠에 따라 증가하며 특히 페이지 나누기는 나눌 내용이 많을수록 비용이 커짐. 수십 페이지를 넘어가면 정지가 버그 제보로 이어질 만큼 길어질 수 있음.

메인 스레드 밖으로 옮기기

  • 해결책은 렌더링을 점진적으로 처리하거나 프레임에 나눠 담는 것이 아니라, 메인 스레드가 아닌 곳에서 실행하는 것임.
  • 웹 워커는 DOM에 접근할 수 없는 별도 스레드임. react-pdf는 애초에 DOM을 사용하지 않으므로 이 제약은 문제가 되지 않음.
  • React 요소는 구조화 복제(structured cloning)를 사용하는 postMessage로 전달할 수 없으며, 함수와 요소는 복제 과정에서 유지되지 않음. 따라서 문서 컴포넌트는 워커 안에 두고, 스레드 사이에는 일반 데이터만 전달해야 함.
  • 워커 안에서 @react-pdf/rendererpdfFont를 가져오고 문서 컴포넌트를 불러온 뒤, Font.register로 Roboto 글꼴을 등록하고 전달받은 데이터로 PDF Blob을 생성해 다시 보냄.
  • 글꼴 등록은 워커 안에서 수행해야 함. Font.register는 실행 중인 스레드의 모듈 범위에 저장소를 채우며, 워커는 메인 스레드와 별도의 모듈 범위를 가지므로 메인 스레드에서 등록한 내용은 워커에 적용되지 않음.
  • 메인 스레드에서는 모듈 워커를 만들고, 메시지 수신 시 반환된 Blob으로 객체 URL을 생성해 상태에 저장함. 이후 청구서 식별자와 항목 목록을 워커에 전달함.
  • Blob은 구조화 복제가 가능하므로 반환할 때 별도 처리가 필요하지 않음.
  • 문서 생성 시간 자체는 이전과 동일함. 달라지는 점은 생성 중에도 인터페이스가 사용자 입력에 응답하며, 애니메이션이 멈춘 스피너 대신 실제 진행 상황을 보여줄 수 있다는 점임.
  • Simon Hessel이 번들러 연결 설정을 포함한 전체 절차를 안내하는 글을 작성했으며, 번들러 설정은 react-pdf 사용 부분보다 차이가 큼.

적용하지 않아도 되는 경우

  • 문서가 작다면 이 설정은 생략하는 편이 적절함.
  • 워커는 빌드 단계, 메시지 프로토콜, 문서 코드에 접근할 수 있는 별도 위치를 추가하며, 해결할 문제가 없는 상황에서는 그만큼의 부담만 생김.
  • 정지 현상을 측정해 확인한 뒤 워커를 적용하는 것이 적절함.