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)