Engineering Journal
Ginexys
Ginexys

Iframes Are the Right Answer and We Keep Pretending They Are Not

2026-06-03

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 PatternIsolation BoundarySetup ComplexityCommon Failure Modes
Micro-Frontend FrameworksJS Namespace / Shadow DOMHigh (200KB runtimes, route coordinators)Event listener leaks, global CSS overrides
Custom Web ComponentsShadow RootMedium (DOM parsing, script re-execution)Unwittingly wiping document.body.innerHTML
Native <iframe> EmbeddingFull Browser Window / ContextMinimal (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:

  1. Dead DOM event handlers: The canonical app's app.js executed initialization against initial DOM nodes. When the web component wiped document.body.innerHTML to render a new sub-view, all attached event listeners pointed to orphaned DOM nodes floating in memory.
  2. Global CSS collision: The embedded app global stylesheets mutated root font sizes and layout properties across the host shell.
Three weeks of intermittent extraction and export bugs were traced directly back to this shared DOM assumption.
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

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.
Read this post in the full Engineering Journal →