At some point, almost every product needs to embed or integrate a third-party UI: payments, analytics, scheduling, support widgets, internal tools, or partner features.
There are four common patterns, each with very different tradeoffs around security, UX, ownership, and complexity:
- 🧱 Iframe embed
- 📜 Script tag / JavaScript SDK
- 📦 NPM package / component library
- 🧬 Micro-frontend (MFE)
Choosing the wrong one early can lock you into painful rewrites later.
⚡ Quick Decision Guide
- 🛡️ Safest, fastest “just embed it” → Iframe
- ⚠️ Quick integration + tight UX (accepts global JS risk) → Script tag / JS SDK
- 🎯 Best UX + full control + shared design system → NPM package
- 🏗️ Independent deploys + large teams → Micro-frontend (MFE)
If you’re unsure, start with iframe. You can always move down the list—but moving up is expensive.
🧱 1. Iframe Embed
🔍 What it is
You render third-party UI inside an <iframe> pointing to a URL hosted by the vendor (or another internal team).
The iframe runs in a separate browsing context, isolated from your app’s JS and CSS.
💻 Basic Example
<iframe
src="https://vendor.example.com/widget?tenant=acme"
title="Vendor Widget"
width="100%"
height="700"
loading="lazy"
style="border:0; border-radius:12px;"
></iframe>
🔐 Secure Sandbox Example
<iframe
src="https://vendor.example.com/widget?tenant=acme"
title="Vendor Widget"
width="100%"
height="700"
sandbox="allow-scripts allow-forms allow-popups"
allow="clipboard-read; clipboard-write"
style="border:0;"
></iframe>
🔁 Parent ↔ Child Communication (postMessage)
Parent
const iframe = document.getElementById("vendor-iframe") as HTMLIFrameElement;
window.addEventListener("message", (event) => {
if (event.origin !== "https://vendor.example.com") return;
if (event.data?.type === "READY") {
iframe.contentWindow?.postMessage(
{ type: "SET_THEME", theme: "dark" },
"https://vendor.example.com"
);
}
});
Child
window.parent.postMessage(
{ type: "READY" },
"https://host.example.com"
);
✅ Pros
- 🛡️ Strong isolation (security, CSS, JS)
- 🔄 Easy rollback
- 🚀 Vendor deploys independently
❌ Cons
- 🎨 Styling consistency is harder
- 🔁 Requires
postMessageplumbing - 🔍 SEO & accessibility limitations
🎯 Use when
- Vendor is untrusted
- Security > UX polish
- You want minimal blast radius
📜 2. Script Tag / JavaScript SDK
🔍 What it is
A vendor-hosted script injects UI or exposes a global SDK that runs inside your app’s origin.
💻 HTML Example
<div id="vendor-root"></div>
<script src="https://cdn.vendor.com/widget/v1/widget.min.js"></script>
<script>
VendorWidget.mount("#vendor-root", {
tenant: "acme",
theme: "light",
});
</script>
⚛️ React Wrapper Example
import { useEffect, useRef } from "react";
export function VendorWidget({ tenant }: { tenant: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const instance = (window as any).VendorWidget.mount(ref.current, { tenant });
return () => instance?.unmount?.();
}, [tenant]);
return <div ref={ref} />;
}
✅ Pros
- 🎯 Better UX than iframe
- ⚡ Fast integration
- 🧩 No build-time dependency
❌ Cons (Real Risks)
- ☠️ Executes untrusted JS in your origin
- 🎨 CSS / global JS conflicts
- 🐞 Painful debugging
- 📦 CDN version drift
🛡️ Required Safety Measures
- 🔒 Pin exact versions
- 🧾 Use SRI
- 🚧 Lock down CSP
- 🧩 Demand namespaced CSS or Shadow DOM
🎯 Use when
- Vendor is trusted
- Widget is small
- Speed matters more than isolation
📦 3. NPM Package / Component Library
🔍 What it is
You install the vendor UI as a dependency and render it directly.
This gives the best UX—and the most responsibility.
📥 Install
npm install @vendor/ui @vendor/sdk
⚛️ React Example
import { VendorCheckout } from "@vendor/ui";
export function Checkout() {
return (
<VendorCheckout
customerId="cust_123"
theme="light"
onSuccess={(result) => console.log(result)}
/>
);
}
✅ Pros
- 🏆 Best UX
- 🧪 Fully testable
- 🔢 Explicit version control
- 🎨 Design system alignment
❌ Cons
- 📦 Bundle size impact
- 🔗 Dependency conflicts
- 💥 Vendor bugs can break builds
- 🚫 No isolation
🧠 Best Practices
- 🔒 Pin versions
- ⏳ Lazy-load heavy UI
- 🧱 Wrap with adapter components
🎯 Use when
- UI is core to your product
- You own UX decisions
- You accept upgrade ownership
🧬 4. Micro-Frontend (MFE)
🔍 What it is
A separately built and deployed frontend loaded at runtime.
🏗️ Host Loader Example
async function loadRemote(url: string) {
await new Promise<void>((resolve) => {
const s = document.createElement("script");
s.src = url;
s.onload = () => resolve();
document.head.appendChild(s);
});
return (window as any).RemoteApp;
}
🔌 Remote App Contract
export function mount(el: Element, opts: { tenant: string }) {
return { unmount() {} };
}
(window as any).RemoteApp = { mount };
✅ Pros
- 🚀 Independent deploys
- 🧑🤝🧑 Team scalability
- 🔄 Shared runtimes
❌ Cons
- 🧠 High complexity
- 🔗 Version coordination pain
- 🐢 Slower local dev
🎯 Use when
- Multiple teams
- Independent release cycles
- Large UI surface area
📊 Comparison Table
| Pattern | Isolation | UX | Complexity | Risk |
|---|---|---|---|---|
| 🧱 Iframe | High | Medium | Low | Low |
| 📜 Script SDK | Low | High | Low | Medium–High |
| 📦 NPM Library | Low | Very High | Medium | Medium |
| 🧬 MFE | Medium | High | High | Medium |
⚠️ Critical Engineering Concerns
🔐 Authentication
- Avoid long-lived tokens in URLs
- Prefer short-lived sessions
- Never expose secrets to iframes
🎨 Styling
- Use theme contracts
- Avoid global CSS
- Prefer Shadow DOM when possible
⚡ Performance
- Lazy-load non-critical UI
- Cache remote assets
- Watch for duplicate bundles
🛟 Reliability
- Loading states
- Timeouts
- Graceful fallbacks
🧭 Practical Recommendations
- 🛡️ Untrusted vendor → Iframe
- ⚠️ Trusted widget → Script SDK
- 🎯 Core UI → NPM library
- 🏗️ Large org → MFE
Top comments (0)