Shadow DOM: The Key Is Unbreakable Styling
Key point
Isolating different resume HTML with Shadow DOM prevents style conflicts and FOUC.
Details
In July 2025, as Saramin's job postings and candidate services underwent a major overhaul, a problem arose: resumes from multiple linked platforms such as Jumpit, Comate, and Wonderful Senior needed to be displayed stably on a single screen. Each platform's resumes had PDF, HTML, and different CSS, class names, and layouts, which could conflict with the existing styles of the candidate detail page.
Since the candidate detail page is the core screen that HR managers view most frequently, broken rendering would immediately impact service reliability and hiring opportunities. So the goal wasn't simple integration, but strong encapsulation that guaranteed unbreakable styling no matter what resume came in.
The solution was Shadow DOM. An independent shadow tree was created on the shadow host, and resumes were rendered isolated from external style and DOM influence. The structure allows choosing between open/closed mode, and this service used closed mode for internal control.
The rendering flow is as follows.
- Parse the HTML string with
DOMParser.parseFromString(). - Extract and reconstruct
meta,link,style, andbody, converting relative paths to absolute paths based onbase. - Separate
script[src]and inline scripts for separate handling. - Load external scripts in parallel using Promise.allSettled, so that some failures don't block the entire rendering.
- Substitute
pdf.worker.jsandpdf.jswith separate paths to avoid conflicts with the project'spdfjs-distmodule. - Insert the HTML body into the shadowRoot as a Document Fragment, which is
template.content.
The trickiest problem was FOUC (Flash of Unstyled Content). Since adoptedStyleSheets or prioritized style insertion alone didn't solve it, a MutationObserver was used to detect style/class changes, stabilizing after 0.3 seconds with no additional changes, and forcing success after a maximum of 5 seconds before showing the screen. Initially hidden with display: none, it was switched to block once styles stabilized, preventing exposure of a state with incompletely applied styles.
The final step is executing internal scripts. Since shadowRoot effectively behaves like a document, document.querySelector, querySelectorAll, getElementById, and body within inline scripts had to be replaced with shadowRoot-based equivalents. However, creation methods like document.createElement() couldn't be used as-is, and since closed mode doesn't allow direct access to the shadowRoot, scripts were executed using the new Function approach, which receives the passed object as an argument.
This summary was generated automatically by AI. Check the original for the author's claims and context. Copyright belongs to the original author.
Our guide explains how the AI works. Report summary errors, attribution issues, or removal requests via Contact.