Iframes Are the Right Answer and We Keep Pretending They Are Not
TLDR
Modern web development often shuns<iframe> tags in favor of complex micro-frontend runtimes, module federation, or custom web components. After spending three weeks debugging a custom web component that wiped document.body.innerHTML to inject a tool view into an active page, we replaced the component with a 14-line <iframe> shell. That bought us instant style isolation, zero script collisions, native security sandboxing, and zero race conditions.
| Architectural Pattern | Isolation Boundary | Setup Complexity | Common Failure Modes |
|---|---|---|---|
| Micro-Frontend Frameworks | JS Namespace / Shadow DOM | High (200KB runtimes, route coordinators) | Event listener leaks, global CSS overrides |
| Custom Web Components | Shadow Root | Medium (DOM parsing, script re-execution) | Unwittingly wiping document.body.innerHTML |
Native <iframe> Embedding | Full Browser Window / Context | Minimal (14 lines of native HTML) | Cross-origin postMessage setup required |
Micro-frontend frameworks introduce unnecessary runtime complexity
When developers need to embed one web application inside another, we frequently over-engineer the solution. We pull in module federation libraries, write complex DOM parsers, or attempt to wrap entire apps inside Shadow DOM roots.
We initially built a custom web component (gx-pdf-shell) to embed our PDF Processor inside our desktop OS shell because we thought an iframe felt outdated. The web component attempted to fetch the sub-app HTML, parse it via DOMParser, inject the body, and execute inline scripts dynamically.
Shared DOM assumptions trigger global style collisions and memory leaks
By forcing the embedded app to share the main application's document.body, we introduced severe integration bugs:
- Dead DOM event handlers: The canonical app's
app.jsexecuted initialization against initial DOM nodes. When the web component wipeddocument.body.innerHTMLto render a new sub-view, all attached event listeners pointed to orphaned DOM nodes floating in memory. - Global CSS collision: The embedded app global stylesheets mutated root font sizes and layout properties across the host shell.
Shared DOM Architecture (High-Risk):
+-------------------------------------------------+
| Main Document (Host Shell CSS/JS) |
| +-------------------------------------------+ |
| | Micro-Frontend Component (Shared DOM) | |
| | - Mutates document.body | |
| | - Overrides global CSS variables | |
| | - Orphaned event listeners | |
| +-------------------------------------------+ |
+-------------------------------------------------+
Iframe Sandboxed Architecture (Isolated): +-------------------------------------------------+ | Main Document (Host Shell CSS/JS) | | +-------------------------------------------+ | | | Iframe Viewport Container | | | | +-------------------------------------+ | | | | | Sandboxed Document (Isolated CSS/JS)| | | | | +-------------------------------------+ | | | +-------------------------------------------+ | +-------------------------------------------------+
Native iframes guarantee total style isolation and memory sandboxing
Replacing the entire web component with a native <iframe> solved every issue immediately:
<!-- 14 Lines of HTML Replacing a 200KB Micro-Frontend Setup -->
<div class="win-viewport">
<iframe
src="/tools/pdf-processor/?view=editor"
title="PDF Processor"
sandbox="allow-scripts allow-same-origin allow-forms"
loading="lazy">
</iframe>
</div>
Why iframes deliver superior isolation
- Total CSS sandboxing: Styles inside the iframe cannot leak into the host document, eliminating the need for CSS modules or scope prefixes.
- JS memory isolation: Globals, event listeners, and DOM trees are fully encapsulated inside their own window object.
- Native security controls: Browser
sandboxattributes provide kernel-level origin isolation. - Clean event interop: Simple
window.postMessageroutines handle cross-frame communication cleanly with zero runtime overhead.
Rule of thumb: Do not replace an iframe with a complex micro-frontend framework unless you require sub-millisecond synchronous DOM sharing or unified viewport scrolling. For embedding sub-applications, the humble iframe remains the most reliable isolation primitive in the web platform.