How To Embed Tweets Without a Performance Penalty
Tags: javascript, webdev, performance, programming
Published: 2023-03-02 15:36:17

## Why embed Twitter on your website

You have a great product, and your users are raving about it on Twitter. You want to use the social proof on your page to tell new users how awesome you are. Let's embed some tweets into your site! Twitter makes this easy! Click the tree dots context menu and select `Embed Tweet`, then follow the instructions. Twitter gives you a code snipped, which you embed into your page and success! You have a tweet embedded on your page.

{% twitter https://twitter.com/mhevery/status/1606438382561026049 %}

## Embedding a tweet is expensive

We don't give embedding a tweet onto our site much thought, but it turns out that that tweet is quite expensive from a performance perspective. To show you just how expensive that embed is, we have embedded a [single tweet](https://mhevery.github.io/perf-tests/twitter-embed.html) and then [four tweets](https://mhevery.github.io/perf-tests/twitter-embed-4.html) into a blank page and asked Google PageSpeed to rank the quality of our site. To make sure that we are measuring just the cost of the embedded tweet, the page we are testing is otherwise blank.

To our surprise, the results are not great!

![a screenshot of a table showing the performance hit for 1 and 4 tweet embeds.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F3441e5e8a99c48bbac97c331a731d803?width=800)

Here are some highlights of the bad experience people will get on low-powered mobile devices:

- The users have to wait almost 18 seconds before the page becomes interactive.
- The tweet rendering freezes the UI (not responsive) for 4 seconds.
- Each additional tweet costs the visitor a little over 3 seconds of waiting.
- To render a tweet, the embed script makes about 27 requests and downloads 600 Kb over the wire, which results in 1.6 Mb of JavaScript, out of which only about 50% gets executed.

Keep in mind this is on the blank page. The situation will get worse as the page becomes more complex with more third-party scripts.

The thing to observe is that the Tweet Embed solution is JavaScript-heavy and requires a lot of JavaScript execution to render the tweet. The more JavaScript on startup, the slower your site.

## Why so expensive

![a picture of the Joker from the movie dark knight with the caption why so expensive.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F8e28399c8e9645af902d17d8cc6233a4?width=800)

The natural question is to ask, why is this so expensive? Why does Twitter not care about its users and the end-user experience? Well, it is easy to blame Twitter engineers but let's have some empathy for the engineer that designed this system. They were probably under time pressure to deliver, and so they chose the path of least resistance to deliver the Tweet embed feature.

The Tweet embed is a standard stand-alone application that gets loaded in an iframe. (One iframe per tweet) The application is CSR (client-side rendered) SPA (single page application) written in React. React is a well-tested and loved technology by developers, so the fact that the Twitter engineer chose it for the tweet implementation is unsurprising.

The Twitter engineers could have chosen other rendering technologies, but it would not have significantly impacted the outcome. We have compared a [movie app written in seven different frameworks](https://www.builder.io/blog/movies-app-in-7-frameworks-which-is-fastest-and-why), and the result was mostly the same for all CSR applications. The main winners were the HTML-first frameworks such as [Astro](https://astro.build/) and [Qwik](https://qwik.builder.io/) because those deliver primarily HTML rather than JavaScript — more on that later.

## 23 third-party scripts

At this point, stepping back and seeing the big picture is important. A single tweet has lowered our site score to 69. Four tweets to 39! But this is but one third-party script. Google reports that an average site has [23 different third-party scripts](https://almanac.httparchive.org/en/2021/third-parties)! So this is but one of many more scripts to be embedded into your site. If one third-party script fares so badly, what do you think a site with 23 different third-party scripts will do? There is just no hope that the site will perform well. The site will have a bad user experience, and people will leave.

It is paramount that we, as site developers, pay attention to third-party scripts and do a better job shielding ourselves from their consequences.

## It's just another CSR app

The big realization is that each third-party script is yet another application on your site besides your application. We do things to optimize our application, such as SSR (server-side render)/SSG(static site generation), so why don't third-party scripts do that as well?

Third-party scripts are primarily concerned with the ease of embedding. The primary goal for the Twitter embed is easy embedding for the site developer. When the site developer recognizes that the embed has destroyed the site’s performance, it is too late, and they are unlikely to do much about that. See [3 people problem] later in this article. Because ease of embedding is a priority, the most straightforward thing to do is to cut and paste a JavaScript tag into your site. The CSR (Client Side Rendering) approach will work with any existing solution you use for the site because all the work is done on the client rather than on the server. But precisely, this do-all-work-on-the-client shortcut makes embedding easy. However, the performance of the third-party scripts is poor.

What we need is a way to embed third-party applications in a cut-and-paste way that actually renders using SSR/SSG (rather than CSR). The problem is that most of our frameworks/libraries are CSR-centric, with SSR as an add-on using meta-frameworks. Notable exceptions are [Astro](https://astro.build/) and [Qwik](https://qwik.builder.io/), which focus on SSR/SSG as a primary delivery mechanism.

## It all comes down to JavaScript

Once you dig into site performance for long enough, [you realize it all comes down to JavaScript](https://www.builder.io/blog/how-to-improve-website-performance-in-2023). Actually, it is three things, but two of them have a well-understood solution, so we are left with JavaScript.

To make your site fast, it needs three things:

1. To send HTML to the client
2. To optimize images and CSS
3. To minimize the amount of JavaScript on the startup


Most meta-frameworks can do SSR/SSG, so they can send HTML to the client. Meta-frameworks also have good support for Image and CSS optimization. It is not an automatically solved problem, but with a little bit of work on the developer's side, the HTML, Image, and CSS are well-understood problems with well-understood solutions. So if a site fails to deliver on these, it really comes down to the site developer not doing their job or not being aware of how to do it.

The remaining problem is executing less JavaScript on startup. We don't have good solutions for sending less JavaScript to the client. That is because most meta-frameworks and frameworks use [hydration to recover their state](https://www.builder.io/blog/hydration-is-pure-overhead), which results in too much JavaScript execution on the client. That is why solutions such as [Astro](https://astro.build/), [Eleventy](https://www.11ty.dev/), and [Qwik](https://qwik.builder.io/) are becoming more popular because they are focusing on addressing the JavaScript problem.

So the Twitter engineer implementing the Twitter embed fell into the JavaScript problem because most of our solutions are JavaScript-centric.

## HTML-first embedding

To get good performance, third-party scripts need to send HTML and delay the execution of JavaScript for as long as possible. When the site starts up, there is always some amount of JavaScript that needs to run on the main thread, which is the bottleneck. Unfortunately, third-party scripts' current status quo is that each adds even more JavaScript to the startup cost. Some tools such as [Partytown](https://partytown.builder.io/) try to help with this, but overall this is not a solved problem. Third-party script providers should strive to improve.

But remember that third-party providers are really just packaging their applications into an easily embeddable script tag. They themselves usually use existing frameworks/libraries. These frameworks/libraries are CSR-centric with limited ability to do SSR and delay hydration. So while it is easy to blame third parties for slow site startup performance, in the end, the third parties rely on the same frameworks/libraries as we all do, and most of these frameworks are not focused on minimizing the amount of JavaScript at startup.

In the end, third-party providers don't provide HTML-first solutions because, as an industry, we don't have good HTML-first frameworks to use to deliver those solutions. Sure we have [Astro](https://astro.build/) and [Eleventy](https://www.11ty.dev/), but these frameworks are more meant for static site generation rather than interactive application delivery.

## Our proof of concept for tweet embeds

Let's build the same solution with an HTML-first approach.

What we are trying to do is to do a client-side include of HTML.

Ideally, we want this:

```html
<html>
 <body>
   Your content
   <include src="<https://qwik-twitter.builder.io/tweet/1606438382561026049.html>"></include>
  </body>
</html>
```

Unfortunately, HTML does not have an `<include>` tag, so we are cheating with a bit of JavaScript.

```jsx

<html>
<body>
Your content
<script type="module" src="https://qwik-twitter.builder.io/tweet/1606438382561026049.js"></script>
</body>
</html>
```

Where the `https://qwik-twitter.builder.io/tweet/1606438382561026049.js` returns:

```jsx
(function(html) {
   const script = window.currentScript;
   const div = document.createElement('div');
   div.innerHTML = html;
   while(div.firstChild) {
     script.parentNode.insertBefore(div.firstChild, script);
   });
 })(`
   <!-- HTML we want to insert -->
   <div>my tweet</div>
`)
```

The above is HTML wrapped as JavaScript, with very little actual JavaScript.

The above code allows us to achieve two things:

1. It is still easy to embed the tweet into your website as it is a simple cut and paste.
2. The embedding code delivers HTML, not JavaScript (Even though it is wrapped in JavaScript to work around the browser limitations.) The fact that we are delivering HTML (instead of JavaScript) has a huge impact on the site startup performance.

## Generating HTML first content

So there are many frameworks that can do SSR/SSG and hence generate HTML for the client. But most of them come with hydration costs. So for our example, we have chosen [Qwik](https://qwik.builder.io/) because it uses [resumability instead of hydration](https://www.builder.io/blog/resumability-vs-hydration) to make the application interactive. [Astro](https://astro.build/) would have also been a good choice because it delays the hydration cost until the island is ready. Anything which does not add JavaScript on startup and produces HTML would have been a great choice.

The nice thing about [Qwik](https://qwik.builder.io/) is that you get to write your application in a familiar [React-like style](https://qwik.builder.io/docs/cheat/qwik-react/), but [Qwik](https://qwik.builder.io/) automatically generates HTML and delays the loading of JavaScript until it is needed (the same application written with React, as we see in Twitter embedded in the example above, results in a lot of JavaScript on startup).

## Why it performs so much better

Let's examine the performance improvements with the new approach.

![a screenshot of a comparison table between regular twitter embed and the new solution qwik embed.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F003b2e540ef04d878cd5804189ca4905?width=800)

WOW! That’s quite a difference! As you can tell from the above, embedding HTML (even if wrapped in JavaScript) has almost no impact on the startup performance of your site! Even adding multiple tweets does not negatively impact the startup's performance. The only minor negative impact is from CLS (Commutative Layout Shift), which is due to serving images without size information, something that should be a straightforward fix.

The key thing to notice is that all of this is driven by the amount of JavaScript which needs to execute. By reducing the amount of JavaScript, the page became performant. The hard part is not knowing that JavaScript needs to be reduced but knowing how to use existing tools to deliver a minimal amount of JavaScript. This requires a paradigm shift.

## Try it for yourself

If your site uses Twitter Embed and you would like to try this approach, you can do so here: [https://qwik-twitter.builder.io/](https://qwik-twitter.builder.io/).

1. Find a tweet that you would like to embed
2. Paste it into the `Tweet URL` and submit.
3. Copy the appropriate level of the script to your site.

### What the differences are

1. `JS only` is by far the smallest amount of code to paste, but it means that the end user will first see a page without a tweet, and a fraction of a second later, the tweet will show up. This has several shortcomings. The small delay will negatively impact the CLS, and the lack of content will prevent the search engine from indexing the tweet content.
2. `HTML + JS` includes HTML in addition to JavaScript. While this tweet will still have a flash of unstyled content, it does have the content, which means the search engine will correctly index it.
3. `HTML + JS + CSS` is the best way to inline the content so that there is no flash of unstyled content and the search engine can index it. The JS on end can update the tweet reply, retweets, and favorite stats once it executes.


## The three people problem

Any system which is not a closed loop will go out of sync. The best systems are such that there is a feedback loop that self-corrects. In the case of a third-party ecosystem, there are three separate people involved and very little feedback between them.

1. Third-party developer: in our case, the Twitter engineer that created the Twitter embed code.
2. First-party developer: this is you creating a site for your users and deciding to embed the third-party code because it provides value.
3. End user: the person using the site.

The problem is that the person feeling the slowness is the end user. The person who can do something about it is the third-party developer, but they are not feeling the problem. The first-party developer is aware of the problem but needs the value of the third-party script, so they keep using it as there are few alternatives.

The above creates very little incentive for the problem to get fixed. Add to it the realization that the third-party developer needs better tools for creating the third-party code in an HTML-first manner, and we have a bit of a stalemate in the industry.

## What the industry can do to aim higher

So what can we do to improve this?

1. We need to empathize with the end user and care more about performance as an industry.
2. We need examples (like this one) showing that it is possible to do better. This is a mental model shift where we need to understand that it is about leaning more toward HTML and less toward JavaScript.
3. We need tooling that allows developers to create HTML-first sites without sacrificing development speed. The easy path needs to be the performant path; otherwise, corners will be cut in the name of deadlines.

## Conclusion

Average sites have 23 third-party scripts. We add these scripts because they provide value for our sites, whether as social proof or insights into user behavior. To make the scripts easy to embed, they are usually written as CSR (client-side rendered) applications. CSRs come with high startup cost, which is often hidden from the site developer. Most of these third-party scripts are written as CSR applications, meaning they are JavaScript-heavy and need to execute on startup, negatively impacting our site. Multiply this by 23 scripts per page, and the site has little hope of being fast, let alone “blazingly™ fast”.

![an image of a man (the primeagen) with a green screen behind him with the caption blazing fast.](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F27950220bff7422cb8e0aa7c1cd2d492?width=800)

There is an alternative way to provide value. Instead of CSR, we need HTML-first third-party scripts that delay JavaScript for as long as possible. Such third-party scripts are possible, but they require different kinds of approaches to the problem. This requires support from tools and educating developers. We need to make sure that the easy way to build a site or third-party scripts is also the performant way to build it.

## Visually build with your components

[Builder.io](https://www.builder.io/m/developers) is a headless CMS that lets you [drag and drop](https://www.builder.io/m/products) with [your components](https://www.builder.io/m/developers) right within your existing site.

[Try it out](https://builder.io/signup)

[Learn more](/m/developers)

```jsx
// Dynamically render your components
export function MyPage({ json }) {
  return <BuilderComponent content={json} />
}

registerComponents([MyHero, MyProducts])
```