Josh Goldberg
Screenshot of with Elements dev tools open, showing a masonry grid with one entry (class name 'w-full shadow-2xl') expanded to show an iframe

Speeding Up Centered Part 1: 81 <iframe> Embeds

Jul 19, 202315 minute read

Improving a page's performance by over-eager loading most of its embedded content.

Page performance is an essential part of any web application. Poor page performance results in worsened SEO, user retention, and general website pleasantness. But when you’re a startup working quickly to implement critical user features, it’s easy to let “performance paper cuts” pile up over time.

✨ Tip: See for a more thorough explanation of why page performance is important.

Earlier this year, I spent some time working on performance for the popular Centered app. My goal was to improve the app’s page load times to help them improve user acquisition and the general experience of using the app. This post is the first in a series describing each of the significant page load performance improvements I made. For each improvement, I’ll cover:

  1. Identification: how I used developer tools at a high level to identify where there was an issue
  2. Investigation: using more tooling to dig deeper into what the root cause of the issue was
  3. Implementation: the actual code change I made to address the performance issue
  4. Confirmation: observing that the fixes improved the production app as expected

The net results were quite pleasing:

This post covers the first of five key improvements I made to the Centered app:

  1. 👉 81 <iframe> Embeds
  2. Hidden Embedded Images
  3. Barrel Exports
  4. Unused Code Bloat
  5. Emoji Processing

Let’s dig in!


First, let’s cover some context: what is Centered and why was I looking into it?

Centered is a web app that provides a suite of tools to help you get into high productivity (“flow”) states. It provides nice background music, reminders, task tracking, and coach-led group working sessions. I’m a fan - and would recommend trying it out if you haven’t yet.

Centered has excellent user retention -users who get into it stay using it- but not as good acquisition: getting new users to try it. I was first connected to Centered by Cassidy Williams, an advisor to the Centered team and coach voice on the app, in the context of brainstorming how to improve their user acquisition.

My first instinct as a web developer is to check web apps for serious SEO issues such as poor accessibility or performance.

Issue 1: The Quotes Page

The first issue I looked at on was the marketing page. It contains 81 <iframe> embeds of tweets from various happy users. The team had noticed the page takes a long time to load and spikes the user’s CPU in the process.

I tried visiting, and … wow! They were right. It was aggravatingly slow.

On my Mac Mini with an M1 Max chip, the page took ten seconds to populate page content. Yikes! Users tend to abandon pages after ~2-3 seconds at most, let alone double digits.

I’m guessing this particular page had started off with only a small number of quotes to load, then slowly expanded and slowed down over time.

1. Identification

Normally at the beginning of an investigation I’ll run a tool such as the Chrome dev tools > Lighthouse performance audit to get an overview of a page’s performance issues. But this page was straightforward enough that I felt I could run off my past experiences and intuition.

The page’s HTML, CSS, and JavaScript all loaded pretty quickly: so the issue was client-side, rather than the server taking a long time to send them over. That meant the issue was most likely some client scripts taking too long to run.

Problem identified. Next step: investigating what was running on the client.

2. Investigation

I took a look at the DOM structure of the page to see what was being created. What I saw amused me: dozens upon dozens of <iframe>s, each embedding one of the tweets shown on the page!

The page uses the popular react-twitter-embed library to render an <iframe> for each tweet. The <iframe> strategy for embedding tweets is common and handy for avoiding the higher cost approach of using Twitter API’s to populate tweet data in a custom-written component.

Fun fact: each <iframe> in a web page causes the browser to recreate what is essentially a webpage-in-a-webpage, complete with its own DOM body and JavaScript execution environment. Placing several dozen <iframe>s immediately on a single webpage is almost the equivalent of opening that many browser tabs all at the same times.

Even worse, each <iframe>’s JavaScript runs in the same thread as its parent. Having multiple sub-pages run their page startup routines simultaneously can be disastrous for page loading times!

Investigation complete: there were too many <iframe>s loading with the page. Next step: reducing that number.

3. Implementation

One of the most common techniques in performance work is lazy loading: loading content only when it is needed. In web pages, that often means loading only content “above the fold” -content the user can see without scrolling- initially, then loading subsequent content as the user scrolls down. The quotes page only really needed the first half dozen or so tweets to load immediately.

I opted for a variant sometimes called “over-eager loading”: loading the initial content first, then “eagerly” loading in more and more content later. You can think of over-eager loading as an optimization over lazy loading. In lazy loading, content isn’t loaded until it’s needed. In over-eager loading, not-yet-needed content is still not loaded immediately, but it is “preloaded” after the main content.

✨ Tip: A more common example of over-eager loading is pre-fetching HTTP resources with <link rel="preload">.

The code change roughly boiled down to incrementing a counter in state tracking how many cards should be shown:

+ const startingCardsCount = 6;
export default function Cards(): JSX.Element {
  const cards = useSocialCards();

+ const [loadedCount, setLoadedCount] = useState(0)
+ const onCardLoad = () => setLoadedCount((previous) => previous + 1)

  return (
+       // We exponentially increase the number of cards shown as they load in
+       // This way we don't load potentially dozens of <iframe> embeds at once
+       .slice(0, startingCardsCount + loadedCount * 1.5)
        .map((card) => (
+               onLoad={onCardLoad}

I had it start with 6 cards and increment by loadedCount * 1.5 for a couple of reasons:

The numbers are arbitrary, but what’s important is that the page was waiting to load later <iframe>s until after earlier ones.

Next step: confirming the page’s improved loading behavior.

4. Confirmation

This change was straightforward to verify. I loaded the quotes page with the code changes and immediately noticed a drastic improvement in perceived load time. The general time from page load to all visible content loading was reduced from >10 seconds to <3 seconds.

The page still has a bit of choppiness as the frames load in. It’d be nice to set up a page system that uses its own React components with cached tweet data instead of any <iframe> embeds. That’s a longer task for another day. For now, I’m satisfied with the 300% speedup. ⚡️

Normally at this stage I’d try to validate with real user data on production, ideally by running an A/B test. Seeing if any user behaviors such as bounce rate changed on production could work as a backup. Unfortunately, we didn’t have a suitable analytics provider set up at the time.

In Conclusion

Web performance is easy to neglect when you’ve got other pressing concerns to handle. But pages that may have been fine one year may slowly turn slow over time.

When your pages grow too slow, strategies such as lazy loading and over-eager loading can trim those bloated webpages back to speedier versions of themselves. Be sure to always investigate the root cause of issues, surgically improve performance bottlenecks, and validate that your changes do what you intend.


Many thanks to:

Up Next

Later posts in this series dive deeper into using dev tools and more intricate code strategies to detect and fix deeper issues. Continue reading on the second post, Speeding Up Centered Part 2: Hidden Embedded Images! ✨

  1. 👉 81 <iframe> Embeds
  2. Hidden Embedded Images
  3. Barrel Exports
  4. Unused Code Bloat
  5. Emoji Processing

Liked this post? Thanks! Let the world know: