Third-party embeds: script embed vs iframe embed. Which should you choose?

An arrow from a server is inserting a box into a webpage that is displayed on a mobile screen.

Today, I will explore the 2 main methods of embedding third-party content in a website: a script embed and an iframe embed. I will compare them across a few dimensions and outline some best practices. Let’s dive in!

What are third-party embeds?

Third-party embeds allow you to include many different types of external content such as code demos (Codepen, JS Fiddle, CodeSandbox) and hosted videos (YouTube, Vimeo) on your webpage. A third-party embed is any content displayed on your site that is not directly authored by you, and is served from third-party servers.

To embed the content, you need to copy a code snippet and include it on your webpage.

Codepen offers a choice of 4 embed snippets: HTML(Recommended), Wordpress Shortcode, Prefill Embed, Iframe
Codepen offers a choice of 4 embed snippets: HTML(Recommended), Wordpress Shortcode, Prefill Embed, Iframe.

Sometimes, there is more than one embed option. The embed snippets vary from website to website but fall into 2 general categories: a script embed and an iframe embed.

The other embed options tend to be offering a platform-specific snippet – for example, Codepen offers a Wordpress Shortcode to use on Wordpress.

It is common practice for people to use a shortcode (used with templating languages such as Liquid) or a web component (a custom HTML element or a framework-specific component such as React) to encapsulate the embed code to avoid duplication and make maintenance easier.

For example, I built this website with Eleventy, and I added a nunjucks shortcode that enables me to embed a codepen with the following code:

Nunjucks
{% codepen "https://codepen.io/robjoeol/pen/gOEbPvZ", 
"Christmas patterns",
610 %}

It has 3 mandatory parameters: URL, title, and height.

What is the difference between an iframe embed and a script embed?

An iframe embed

An iframe embed is an iframe element with various attributes populated.

For example, this is the snippet that Codepen produces for my codepen found at https://codepen.io/robjoeol/pen/gOEbPvZ:

HTML
<iframe height="300" style="width: 100%;" scrolling="no" title="Christmas patterns" src="https://codepen.io/robjoeol/embed/gOEbPvZ?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscr                                                                            een="true">
See the Pen <a href="https://codepen.io/robjoeol/pen/gOEbPvZ">
Christmas patterns</a> by Rob (<a href="https://codepen.io/robjoeol">@robjoeol</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>

At a minimium, the iframe will have the src attribute populated to specify the page to embed. A height is usually included too. The default size of an iframe is 300 pixels by 150 pixels if the width and height are omitted.

I will discuss the other attributes in more detail later as they can influence how the iframe will look and behave.

A script embed

A script embed involves running a third-party script. Generally, the script creates a iframe element, populates some attributes, and inserts it into the DOM of the page at the location of the script tag.

The purest example of this is JSFiddle. For example, if I want to the embed the demo with the URL of https://jsfiddle.net/robjoeol/6da74g2y/, its default embed snippet is: <script async src="//jsfiddle.net/robjoeol/6da74g2y/8/embed/"></script>.

JSFiddle embed dialog showing by default an async script tag.
The embed dialog for a JSFiddle presents you with a script snippet by defaul

This is the result after the script has run:

HTML
<script async src="//jsfiddle.net/robjoeol/6da74g2y/8/embed/"></script>
<iframe src="https://jsfiddle.net/6da74g2y/8/embedded/?username=robjoeol"
id="JSFEMB_19793"
width="100%" height="367.60"
frameborder="0"
sandbox="allow-modals allow-forms allow-same-origin allow-scripts
allow-popups allow-top-navigation-by-user-activation allow-downloads"

allow="midi; geolocation; microphone; camera; display-capture;
encrypted-media;"
>

</iframe>

The nice thing about the embed script provided by JSFiddle is that you can read it! It is not minified and obfuscated like most scripts these days! Here is what it looks like:

Javascript
(function(){

var createEmbedFrame = function(){
var uid = "JSFEMB_" + (~~(new Date().getTime() / 86400000))
var uriOriginal = "https://jsfiddle.net/robjoeol/6da74g2y/8/embed"
var uriOriginalNoProtocol = uriOriginal.split("//").pop()
var uriEmbedded = "https://jsfiddle.net/6da74g2y/8/embedded/?username=robjoeol"
var currentSlug = "6da74g2y"
var target = document.querySelector("script[src*='" + uriOriginalNoProtocol + "']")
var iframe = document.createElement("iframe")

iframe.src = uriEmbedded
iframe.id = uid
iframe.width = "100%"
iframe.height = "0"
iframe.frameBorder = "0"
iframe.allowtransparency = true
iframe.sandbox = "allow-modals allow-forms allow-same-origin allow-scripts allow-popups allow-top-navigation-by-user-activation allow-downloads"
iframe.allow = "midi; geolocation; microphone; camera; display-capture; encrypted-media;"

target.parentNode.insertBefore(iframe, target.nextSibling)

var setHeight = function(data){
if (data.slug === currentSlug) {
var height = data.height <= 0 ? 400 : data.height + 50
iframe.height = height
}
}

var listeners = function(event){
var eventName = event.data[0]
var data = event.data[1]

switch (eventName) {
case "embed":
setHeight(data)
case "resultsFrame":
setHeight(data)
}
}

window.addEventListener("message", listeners, false)
}

setTimeout(createEmbedFrame, 5)

}).call(this)

A common variation on this is that the embed snippet contains a block element such as a p that acts a placeholder, along with the script. The script transforms that p into an iframe.

For example, this is the snippet for Codepen’s “HTML (Recommended)” embed for the same codepen mentioned earlier:

HTML
<p class="codepen" data-height="610" data-default-tab="result" 
data-slug-hash="gOEbPvZ" data-user="robjoeol"
style="height: 610px; box-sizing: border-box; display: flex;
align-items: center; justify-content: center; border: 2px solid;
margin: 1em 0; padding: 1em;
"
>

<span>See the Pen <a href="https://codepen.io/robjoeol/pen/gOEbPvZ">
Christmas patterns</a> by Rob (<a href="https://codepen.io/robjoeol">@robjoeol</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js">
</script>

The p element has some data attributes that is used by the script, and some inline styles. The script replaces the p element with the following markup:

HTML
<div class="cp_embed_wrapper">
<iframe allowfullscreen="true" allowpaymentrequest="true"
allowtransparency="true"
class="cp_embed_iframe "
frameborder="0" height="610" width="100%"
name="cp_embed_1" scrolling="no"
src="https://codepen.io/robjoeol/embed/gOEbPvZ?user=robjoeol&amp;slug-hash=gOEbPvZ&amp;default-tab=result&amp;height=610&amp;name=cp_embed_1"
style="width: 100%; overflow:hidden; display:block;"
title="CodePen Embed" loading="lazy"
id="cp_embed_gOEbPvZ">

</iframe>
</div>

The iframe produced has some different attributes than the “Iframe” embed, and is wrapped in a div. Since the script is minified, we cannot tell what it does exactly.

Why would you chose a script embed over an iframe embed?

Let’s explore why you would chose one over the other across a few dimensions. In both cases, it is worthwhile auditing the embed code snippet before you use it in production.

Aspect Script embed Iframe embed Which is preferable?
Performance The main perf benefit of this method is to make the loading of the iframe non-blocking (asynchronous). The iframe is loaded some time after the intial page load. If you can lazy load the iframe, the performance is optimal. Typically, an iframe with lazy loading is preferable because the resources are only loaded when the iframe is in view.

There are iframe embeds like YouTube where a “facade” script such as lite-youtube-embed might be preferable to make the loading of the iframe user-initiated.
UX This method injects an iframe into the DOM. This can a cause a cumulative layout shift (CLS) if the correct space is not reserved for the iframe. The provider may not be doing this for you. An iframe has an intrinsic size with minimum dimensions, it will not cause a cumulative layout shift. The “iframe” embed is more foolproof because it never contributes to CLS.

If you audit the behaviour of the script on a page, perhaps it does not cause a layout shift and delivers the same experience.
Security/Privacy Running a third-party script gives access to a page and an user via cookies. It is more prone to security and privacy concerns. An iframe is inherently more secure as is an isolated environment (sandbox) that restricts access to the DOM. It is possible to break out of the sandbox if certain attributes are provided. An iframe is inherently more secure than a script because it is an isolated environment. However, you still need to be vigilant on what permissions you are granting to an iframe. If you are negligent, an iframe can have the same access to a page as a script.
Accessibility I don’t know if there is any negative effect on accessibility when you inject an iframe into a page with a script, but I have spotted accessibility issues more often on injected iframes. An iframe is easier to audit with accessibility tools because it is not an injected element. I have noticed that some script embeds don’t provide an unique title for the injected iframe. An iframe is preferable because an issue with a missing or invalid title can be identified by auditing tools more easily.
Syndication (Feeds) - - Some feed readers strip out iframe and script elements from content. If you want to make your feed more interoperable, you can substitute the iframe for a link to the content when you generate your feed.

A script embed may have a placeholder element with a link that can save you doing the work yourself. You can partly emulate this for iframe embeds with by using the scripting CSS media feature to provide some alternative markup when scripting is prohibited.
Progressive enchancement - - A script embed is more amenable to progressive enhancement than an iframe embed. Only some embed providers have a link to offer a base experience for all users. It is probably best to reach for an open source solution or write your own.

There is not a clear winner. You should take it on a case-by-case for each provider. Let’s take a closer look at each of the dimensions.

Performance

Lazy loading an iframe should perform better than a script embed most of the time. It will not load an iframe that is not immediately required i.e. out of view.

In some cases, a “facade” script like lite-youtube-embed may be preferable. A facade script provides a placeholder with a cover image and a button, it will load the iframe when the button is clicked. I discussed this scenario in a previous post on YouTube embeds.

The primary performance benefit of a script embed is that it makes the loading of the iframe non-blocking (asynchronous). This improves the rendering time of the page. This is usally done by adding async or defer attribute to the script tag. The default embed that JSFiddle offers falls in this category.

HTML
<!-- JSFiddle embed -->
<script async src="//jsfiddle.net/robjoeol/6da74g2y/8/embed/"></script>

The performance benefit of an async script is limited because the iframe will still be loaded and take up resources shortly after the page has loaded. Since every iframe is a complete document environment, it requires significant computing resources. Lazy loading will reduce loading these heavier resources upfront.

I would audit the performance of embeds properly. Favour using the browser devtools or real user monitoring test tools. Lab tools such as Lighthouse can be misleading, especially if you are not familiar with them!

User Experience (UX)

A potential drawback of a script embed is that it may cause a layout shift. If the iframe is inserted into the DOM, it can cause content to move. It is not good for user experience when what you are looking suddenly moves offscreen! You can catch this through the Cumulative Layout Shift (CLS) performance metric.

To prevent this, you need to reserve the right amount of space for the iframe that will be inserted later. Some embed providers do this by having a div element (or similar block element) with a declared height, and the script creates an iframe with the same height to replace that element.

An iframe embed is not susceptible to causing CLS because it does not involve DOM manipulation and it has intrinsic dimensions. An iframe is 300 pixels by 150 pixels (W x H) by default.

Security/Privacy

Embedding a script on your website is an act of faith. You are likely to be giving it full access to do what it wants to your site. The script could include tracking or something malicious. It is a good idea to verify what it does before you use it!

The problem is that typically only a minified version of the embed script is available. You can’t verify what it does unless you have compiler-like reading ability! As I demonstrated earlier, JS Fiddle was one of the few providers I saw that has an unminified script that you can read through. In their case, they do what you would expect them to.

Security is generally lax with scripts embeds. Measures such as subresource integrity are rarely used. If you are not familiar with subresource integrity, it verifies that resources fetched are delivered without unexpected manipulation. This is done through the integrity attribute that has a base64-encoded sha384 hash.

HTML
<script
src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous">

</script>

Also, it is realm of possibility to perform a malicious rug pull. The provider could completely update the script at the URL that you reference in the src attribute of the script.

An iframe is inherently more secure than a script. Loading untrusted code in an iframe provides a measure of separation between your website and the content you’d like to load. The framed content won’t have access to your page’s DOM or your browser’s cookies. It is more secure than a script by default.

However, you still need to be vigilant on what permissions you are granting to an iframe. If you are negligent, an iframe can have the same access to a page as a script. If you want to restrict what the iframe can do, then you can use the sandbox attribute. An iframe that has an empty sandbox attribute is fully sandboxed. It can do very little. Each of the restrictions can be lifted by adding a flag to the sandbox attribute’s value. For example, allow-scripts allows the frame to run JavaScript.

One thing to watch out for is using both the allow-scripts and allow-same-origin flags. This is not recommended, because it effectively enables an embedded page to break out of all sandboxing and makes an iframe similar to a script. Some embeds such as Twitter’s Tweet embed requires allow-scripts to run JavaScript and allow-same-origin to access twitter.com’s cookies to enable posting a tweet (the form) as an authenticated user. You may want this behaviour sometimes, but you should be aware of the risk of doing this.

Another option is to write your own script that mirrors what the provider does.

Accessibility

People navigating with assistive technology such as a screen reader use the title attribute of an iframe to understand its content. The title’s value should concisely describe the embedded content:

HTML
<iframe
title="Wikipedia page for HTML element"
src="https://en.wikipedia.org/wiki/HTML_element">
</iframe>

Without this title, they have to navigate into the iframe to determine what its embedded content is. This context shift can be confusing and time-consuming, especially if embeds contain interactive content like video or audio.

I noticed that some script embeds generate an iframe without an unique title such as Codepen (has been fixed since). I discussed that in a previous post.

It is harder to miss this issue with an iframe embed. You would hope audting tools would catch issues like this regardless. Also, I noticed that Google’s Lighthouse tool does not flag if there are non-unique iframe titles in their accessibility audit.

Syndication (Feeds)

Feed readers (RSS readers) often remove iframe and script tags from articles to protect users. This can limit the reading experience if there is just a blank space in the article where the embed is expected.

The W3C Feed Validator gives the following recommendation when it encounters an iframe in a feed:

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

line 26, column 0: content should not contain iframe tag (13 occurrences) [help]

It will improve interoperability if you provide a fallback for an iframe – provide a link (a element) to the content instead. I don’t see many people do this frankly!

If a script embed has a placeholder element, it can provide a fallback. Codepen’s script embed has an alternative link if the script is blocked.

HTML
<p data-slug-hash="gOEbPvZ">
<span>See the Pen <a href="https://codepen.io/robjoeol/pen/gOEbPvZ">
Christmas patterns</a> by Rob (<a href="https://codepen.io/robjoeol">@robjoeol</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js">
</script>

An iframe embed does not have that ability to provide a fallback. I noticed that Codepen tried to get around this for their iframe embed by putting a link inside the tags. Technically this is invalid HTML because the iframe element does not permit content. However it may work in some feed readers.

HTML
<!-- Don't do this. ❌ This is invalid HTML. -->
<iframe
title="Christmas patterns"
src="https://codepen.io/robjoeol/embed/gOEbPvZ?default-tab=result">

See the Pen <a href="https://codepen.io/robjoeol/pen/gOEbPvZ">
Christmas patterns</a> by Rob (<a href="https://codepen.io/robjoeol">@robjoeol</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>

You can provide a partial fallback for iframe embeds when scripting is prohibited (usual case in RSS readers) by using the scripting CSS media feature to provide some alternative markup. Below is an example for a YouTube video:

HTML
<p class="script-none">Watch 
<a href="https://www.youtube.com/watch?v=DHjqpvDnNGE">JavaScript in 100 Seconds</a>
</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/DHjqpvDnNGE" title="YouTube video player" frameborder="0" allowfullscreen></iframe>
CSS
/* show link when JS is not available */
@media (scripting: none) {
.script-none {
display: block;
}
}

.script-none{
display:none;
}

I think that it is best to transform the content you provide for a RSS feed, replace iframes with links explicitly. My experience as a RSS consumer is that most developers just port the same content from the webpage to the feed.

A script embed that has a placeholder element can save you doing the work yourself if it is done correctly. In any case, you should audit this if you want to maximise the experience for the readers of RSS feed.

Progressive enhancement

Progressive enhancement is a design philosophy that puts emphasis on content first, allowing everyone to access the basic content and functionality of a web page. The idea is to create a base experience that will work for all users and then build on top of that, improving or enhancing the experience for users that can run all the required code.

For embeds, this usually amounts to:

  1. The base experience can be a link to the embedded content.
  2. Some CSS styles can make the link more appealing. A preview placeholder is preferable. If, for any reason, the CSS is not loaded or applied, you still have the base experience.
  3. Some JavaScript will replace the link with an iframe. This can be done when the page loads, or it can be triggered by an user clicking on a button in the preview placeholder. Again, if the user has JavaScript disabled or the script doesn’t run, the link will still be fully functional.

Alvaro Montoro did a nice write-up on creating a progressively enhanced codepen embed that goes through how you can execute the above steps.

Shows 3 steps of a progressively enhanced codepen

A script embed is more amenable to progressive enhancement than an iframe embed. Only some embed providers expose a link to provide a base experience for all users. For this reason, often you are unlikely to use the provider’s default embed snippet.

There is a growing list of open source web components and solutions for embeds that provide a great progressive enchancement experience e.g. lite-youtube and lite-vimeo-embed.

Best practices for embeds

  1. As a general rule of thumb, I would not use the default snippet offered by providers. At the very least, audit the embed across the dimensions discussed in this article. You are more likely to reach for someone’s plugin or web component, review these too!
  2. If you are using a script embed:
    1. I would not use the script from a provider if you cannot review the source code. It is better for the integrity and security of your website to use a script that you have inspected or written yourself.
    2. The initial HTML should have a block element with a specified height that acts as a placeholder. This will prevent a layout shift when the iframe is inserted (in its place).
    3. If you want to provide a base experience to support progressive enhancement, the initial HTML should include a link to the content.
    4. Ensure the script does not block rendering of the page. This can harm the performance of the page. Add the async or defer attribute to the script tag.
    5. To optimize performance, consider using the facade pattern or island pattern for script embeds. This will defer loading assets until the embed is required or the user interacts with the embed. You can consider the is-land component for a general purpose lazy loading solution.
    6. Review the resulting iframe after the script has run.
      • Does it have an unique title?
      • Review the attributes that grant permissions such as sandbox and allow. Don’t allow it to do more than it needs to.
  3. If you are using an iframe embed:
    1. To improve performance, you will want to lazy load the embeds not initially needed. You can do this by adding loading="lazy" to opening iframe tag.
    2. It is preferable to set a width and height for the iframe. If omitted the default dimension is 300 pixels by 150 pixels (W x H).
    3. Review the attributes:
      • Does it have an unique title?
      • Review the attributes that grant permissions such as sandbox and allow. Don’t allow it to do more than it needs to.

Conclusion

Third-party embeds can enrich your webpages with external content. They are regularly used to include videos and code demos in pages. I would urge you to be mindful of the impact that they can have on your website. They can affect the performance, user experience, integrity, and security of your website dramatically.

I recommend that you do not blindly use the default embed snippets provided. The cautionary tale is YouTube embeds that are terribly bloated and slow. There are open source solutions for embeds that deliver superior performance and user experience e.g. lite-youtube for YouTube embeds. Often they use lazy loading or utilise the facade pattern to optimize performance. Also, do not be afraid to write your own script - that way you know what is happening!

When it comes to choosing a script embed or iframe embed, the predictable answer is “it depends”. You should take it on a case-by-case for each provider. I recommend that you audit the embed across the dimensions discussed in this article to seek out the optimal result.

Tagged