- Published on
You Don't Need an Iframe Resizing Library
- Authors

- Name
- Luciano Bianchi

Svix is the enterprise ready webhooks sending service. With Svix, you can build a secure, reliable, and scalable webhook platform in minutes. Looking to send webhooks? Give it a try!
Embedding an iframe inside a web application is a common pattern that many applications use, whether it's to embed a third-party widget or to embed a different component of your own application.
One common problem with iframes is how to let them determine their own size. In other words, how to make the iframe component dynamically resize based on its content rather than using a fixed height or width. There are popular libraries out there that solve this problem.
The thing is, you probably don't need them.
The popular ones are quite complex and bloated because they try to support a wide range of use cases. Some of them don't even have an MIT license (!). It's also likely that in the future, you'll need a small change in behavior that the library doesn't support, and you'll be stuck with it. It's one of those cases where writing your own implementation is just a better idea.
Let's walk through how you can implement the iframe resizing functionality yourself, with very few lines of code and no external dependencies.
How it works
We'll use the Window.postMessage API to communicate between the parent page and the iframe. A function in the child page will determine the size of its content and send it to the parent page. The parent page will listen for the message and then update the iframe's size.
We are going to use React syntax for the implementation, but the same principles can be applied to any other framework.
Implementation
First, we'll define the message type that will be sent to the parent page. Use a namespace for your app to avoid any collisions.
interface MyAppIframeResizerMessage {
type: 'myapp.iframe-resizer-height'
payload: {
height: number
}
}
type MyAppIframeMessage = MyAppIframeResizerMessage
Child page (embedded in iframe)
We'll use the ResizeObserver API to detect changes in the iframe size and send the new height to the parent page.
// In Child page
const postParent = (message: MyAppIframeMessage) => {
window.parent.postMessage(message, '*')
}
export function useIframeResizeNotifier() {
useEffect(() => {
if (!isInIframe()) {
return
}
const boundary = document.body
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { height } = entry.contentRect
postParent({
type: 'myapp.iframe-resizer-height',
payload: {
height: Math.ceil(height),
},
})
}
})
resizeObserver.observe(boundary)
return () => {
resizeObserver.disconnect()
}
}, [])
}
We are using the document.body as the boundary element, but it can be easily changed if you want to resize based on a different element or rule.
Parent page
The parent page will listen for the message, assert that it's coming from the iframe and has the correct format, then update the iframe's height to match the content.
// Parent page
export default function IframeResizer(props: React.IframeHTMLAttributes<HTMLIFrameElement>) {
const ref = useRef<HTMLIFrameElement>(null)
useEffect(() => {
const handleMessage = (event: MessageEvent<MyAppIframeMessage>) => {
try {
if (!ref.current) {
return
}
// Check message is coming from the iframe
if (event.source !== ref.current.contentWindow) {
return
}
if (event.data.type === 'myapp.iframe-resizer-height') {
ref.current.style.height = `${event.data.payload.height}px`
}
} catch (error) {
console.error('error processing iframe message:', error)
}
}
window.addEventListener('message', handleMessage)
return () => {
window.removeEventListener('message', handleMessage)
}
}, [])
return <iframe {...props} ref={ref} />
}
This example only updates the height, but it's easy to see how it can be updated to handle the width or both.
Adding more functionality
We've only explored how to use this approach for resizing the iframe. However, it's relatively easy to expand it to do other things, like error reporting or loading states.
For example, if the parent page wants to show a loading animation until the iframe has finished loading its content, you can do something like this:
// Shared types
type IframeLoadingStatus = 'loading' | 'loaded' | 'error'
interface MyAppIframeLoadingMessage {
type: 'myapp.iframe-loading'
payload: {
status: IframeLoadingStatus
}
}
type MyAppIframeMessage = MyAppIframeResizerMessage | MyAppIframeLoadingMessage
/// Child page
/// Call this when the page has finished loading.
export function notifyIframeStatus(status: IframeLoadingStatus) {
postParent({ type: 'myapp.iframe-loading', payload: { status } })
}
/// Parent page
import { Skeleton } from '@chakra-ui/react'
export default function IframeResizer(props: React.IframeHTMLAttributes<HTMLIFrameElement>) {
const ref = useRef<HTMLIFrameElement>(null)
const [loadingStatus, setLoadingStatus] = useState<IframeLoadingStatus>('loading')
useEffect(() => {
const handleMessage = (event: MessageEvent<MyAppIframeMessage>) => {
try {
if (!ref.current) {
return
}
if (event.source !== ref.current.contentWindow) {
return
}
switch (event.data.type) {
case 'myapp.iframe-loading':
setLoadingStatus(event.data.payload.status)
break
case 'myapp.iframe-resizer-height':
ref.current.style.height = `${event.data.payload.height}px`
break
}
} catch (error) {
console.error('error processing iframe message:', error)
}
}
window.addEventListener('message', handleMessage)
return () => {
window.removeEventListener('message', handleMessage)
}
}, [])
// This works because `Skeleton` renders its children when loading, and doesn't unmount them.
return (
<Skeleton isLoading={loadingStatus === 'loading'}>
<iframe {...props} ref={ref} />
</Skeleton>
)
}
A few more considerations
You can make this implementation more robust by adding validation to the message payloads (with a library like zod or any other that you already use) instead of a broad try-catch block.
Once you have this working in production, if you decide to make changes to the message payload, consider backwards compatibility with older versions of the parent page listener, as they may be updated separately.
Final thoughts
This is a simple solution that is very easy to implement and understand and, most importantly, easy to change to fit your needs. It's lightweight (all the essential code fits in this blog post!) and uses APIs that are available in all modern browsers.
At Svix, we use this approach in our svix-react library to embed the Consumer Application Portal inside your React application. We moved away from using a dependency that was plagued with hard-to-debug performance issues, and ever since we wrote our own implementation, we haven't looked back!
For more content like this, make sure to follow us on Twitter, Github, RSS, or our newsletter for the latest updates for the Svix webhook service, or join the discussion on our community Slack.