Improving the LCP of Brightcove Video Embeds

Fixing a slow Largest Contentful Paint for video poster images

A few months ago, I was reviewing one of the sites I work on in Google's Search Console in order to find URL patterns that had performance problems. One of the layouts that popped up was an article template where the poster image for an embedded video was the Largest Contentful Paint (LCP) element.

If the videos were hosted with Vimeo or YouTube, there are existing solutions that provide a lite embed of the video player and speed up the LCP. However, the videos on this particular site are hosted with Brightcove, and there was no documented solution that I could find.

Being lured by the temptation of improving several thousand URLs by deploying one fix, I decided to look into the issue to see what I could do.

Baseline performance

Here are the numbers from a synthetic test I ran using a MotoG4 over a fast 3g connection to see my starting point.

Metric Time (s)
TTFB 0.788
FCP 2.104
LCP 17.152

The thing that stood out to me right away was the 15-second gap between the First Contentful Paint (FCP) and the LCP.

Why was it slow?

Here is the video embed code from the Brighcove documentation:

<video-js
  data-account="1507807800001"
  data-player="EUYJo0AOB"
  data-embed="default"
  controls=""
  data-video-id="6071787405001"
  width="640"
  height="360"
></video-js>
<script src="https://players.brightcove.net/1507807800001/default_default/index.min.js"></script>

The LCP takes a long time because a lot needs to happen beforehand. The HTML for the player is generated with JS, so the image isn't discoverable until late in the page lifecycle. Here's a synopsis of everything going on:

  1. Since the embed code goes in the body of the page, the browser has to first get through any render blocking tags in the document <head>.
  2. The browser then has to download, parse, and execute the JS file that appears after the <video-js> tag. This file is also quite large at 224kb gzipped and 839kb uncompressed.
  3. The JS will execute a call that connects to a different subdomain to find the video specified in the data-video-id attribute.
  4. The query comes back from Brightcove's API containing information about the video. This data includes the path to the poster image (LCP) for the video, which is hosted on yet a different subdomain.
  5. Rendering happens. The LCP image is downloaded from the subdomain and shown on the screen.

All of this means that the LCP image wasn't discovered until the 116th resource in the waterfall.

How did I fix it?

Looking at the Brightcove documentation, I discovered that you can add a poster attribute to the <video-js> tag to provide your own poster image. Since the site I was working on already had featured images uploaded to the CMS that matched the Brightcove image, I used that as the poster image instead.

That corrected steps 4 and 5 above, but it still relied on JS to render the image. There was still room for improvement, so I decided to see what would happen if I added an <img> tag as a child of <video-js>.

As it turns out, the Brightcove JS replaces the inner HTML of the <video-js> tag when executed. That means I can use the same source image for the <img> and poster, which is exactly what I did.

With the inclusion of the <img> tag, the poster image was discoverable directly in the HTML source.

Here is the adjusted code with the poster attribute and image tag added:

<video-js
  data-account="1507807800001"
  data-player="EUYJo0AOB"
  data-embed="default"
  controls=""
  data-video-id="6071787405001"
  poster="/path/to/featured/image.jpg"
  width="640"
  height="360"
>
  <img src="/path/to/featured/image.jpg" width="640" height="360" alt="" />
</video-js>
<script src="https://players.brightcove.net/1507807800001/default_default/index.min.js"></script>

Final results

Here are the numbers after the fix:

Metric Time (s)
TTFB 0.747
FCP 2.005
LCP 3.452

After the code change, the LCP happens much quicker than its original 17.152s, and the timing is more appropriate for the LCP image. As a result, the LCP image is the 10th resource in the waterfall, which is a vast improvement over its 116th position from before.

Validating the fix

After validating the fix in Search Console, the pages with a video as the LCP element are no longer flagged for providing a poor experience.

More importantly, the time on page for video content has increased almost 10% among site members, and the exit percentage has decreased by 6% since the fix has been live.

The takeaway

In this scenario, I was lucky to have the featured image already in the CMS and served at the correct aspect ratio. In the event that you don't have this option, it might be possible to use the video hosting service's API to fetch the image in advance. If you use Brightcove, that is definitely possible, and this is the route I would have taken if I didn't have the CMS images at my disposal.

Either way, the benefits made this worth a few hours of my time to investigate and fix the issue.