AI Briefing

Shadow DOM: 깨지지 않는 스타일이 핵심

·2025.11.12 00:00

서로 다른 이력서 HTML을 Shadow DOM으로 격리해 스타일 충돌과 FOUC를 막는다.

2025년 7월 사람인의 공고·후보자 서비스가 대대적으로 개편되면서, 점핏·코메이트·원더풀 시니어 등 여러 연계 플랫폼의 이력서를 한 화면에서 안정적으로 보여줘야 하는 문제가 생겼다. 각 플랫폼의 이력서는 PDF, HTML, 서로 다른 CSS와 클래스명, 레이아웃을 갖고 있어 후보자 상세 페이지의 기존 스타일과 충돌할 수 있었다.

후보자 상세 페이지는 인사담당자가 가장 자주 보는 핵심 화면이기 때문에, 렌더링이 깨지면 곧바로 서비스 신뢰도와 채용 기회에 영향을 준다. 그래서 목표는 단순한 통합이 아니라, 어떤 이력서가 들어와도 깨지지 않는 스타일을 보장하는 강한 캡슐화였다.

해결책은 Shadow DOM이었다. Shadow host에 독립된 shadow tree를 만들고, 외부 스타일과 DOM 영향에서 격리된 상태로 이력서를 렌더링했다. 구조는 open/closed mode를 선택할 수 있고, 이 서비스는 내부 제어를 위해 closed mode를 사용했다.

렌더링 흐름은 다음과 같다.

  • DOMParser.parseFromString()으로 HTML 문자열을 파싱한다.
  • meta, link, style, body를 추출해 재구성하고, base 기준으로 상대경로를 절대경로로 바꾼다.
  • script[src]와 인라인 스크립트를 분리해 따로 처리한다.
  • 외부 스크립트는 Promise.allSettled로 병렬 로드해, 일부 실패가 전체 렌더링을 막지 않게 한다.
  • pdf.worker.jspdf.js는 프로젝트의 pdfjs-dist 모듈과 충돌하지 않도록 별도 경로로 치환한다.
  • HTML 본문은 template.contentDocument Fragment로 shadowRoot에 삽입한다.

가장 까다로운 문제는 **FOUC(Flash of Unstyled Content)**였다. adoptedStyleSheets나 스타일 우선 삽입만으로는 해결되지 않아, MutationObserver로 스타일/클래스 변경을 감지하고 0.3초 동안 추가 변경이 없으면 안정화, 최대 5초 후에는 강제로 성공 처리한 뒤 화면을 보여줬다. 초기에는 display: none으로 숨겼다가 스타일이 안정되면 block으로 전환해, 스타일이 덜 적용된 상태의 노출을 막았다.

마지막 단계는 내부 스크립트 실행이다. shadowRoot가 사실상 document처럼 동작하므로, 인라인 스크립트 안의 document.querySelector, querySelectorAll, getElementById, bodyshadowRoot 기준으로 치환해야 했다. 다만 document.createElement() 같은 생성 메서드는 그대로 쓸 수 없고, closed mode에서는 shadowRoot에 직접 접근할 수 없기 때문에, 전달받은 객체를 인자로 받는 new Function 방식으로 스크립트를 실행했다.

이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.

요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.