Theme components and Largest Contentful Paint (LCP)

I’m looking to improve the web vitals for my site, specifically LCP. I have been having issues where the time is ~2.7s (should be under 2.5s). From Optimize Largest Contentful Paint  |  Articles  |  web.dev :

Specifically, LCP measures the time from when the user initiates loading the page until the largest image or text block is rendered within the viewport.

I have isolated the problem to a banner theme component I wrote. A banner image I mount as a widget “above-main-container” is the largest object painted on the screen for basically every page

Code
<script type="text/discourse-plugin" version="0.8">
  const h = require("virtual-dom").h; 

  api.createWidget("my-banner", {
    tagName: "div",

    html() {
      return  h("img.banner-center", {src: settings.banner_image, fetchpriority: "high", style: "aspect-ratio: 925 / 359 ; width: 100%"})
    }
  });
</script>

<script type="text/x-handlebars" data-template-name="/connectors/above-main-container/banner">
  {{mount-widget widget="my-banner"}}
</script>

Here’s what I think is happening. The div holding the image is mounted using JS, so a series of prerequisite things need to happen when a discourse page is refreshed before this JS code ends up running. This causes the banner to be fetched after 2.5s, thus harming the LCP metric.

I have attempted to prioritize the banner image using fetchpriority="high", as seen in the code. But I think it does not address the root timing issue here.

Any suggestions on how to prioritize the rendering of this particular theme component? Would it be better to convert it to a plugin? Is there some other way I can inject the banner in as early as possible? Thanks!

3 Likes

Maybe my question is too detailed. Here’s an easier question if someone knows the answer:

When a page is reloaded, will elements from plugins render earlier than theme components?

If your banner is larger than the element we use for the Introducing Discourse Splash - A visual preloader displayed while site assets load you gonna have a bad time for LCP.

If you think that the main issue is the download of the image asset, you can add something like

<link rel="prefetch" href="http://example.com/myimage.webp" />

to your HEAD element in a theme.

2 Likes

I am currently having a bad time :slight_smile: hahaha

I’ve switched over to using a CDN which has not helped. As I mentioned, I think the call to fetch the banner is the thing happening too late, not the fetch time. I will try the prefetch to see if it makes a difference though!

What I did in the meantime until I arrive at a solution is to not show the banner unless you’re logged in. It looks like google is calculating LCP primarily from search traffic, which is typically non-logged in users in my case

1 Like

LCP will come from all users using Google Chrome, so Android, Windows, MacOS, Linux and Chromebooks.

If you have more pageviews coming from anons on those devices than logged in users, yes, your LCP will reflect performance of those anons.

2 Likes

Good to know.

Do you think I could just work-around this problem by making the splash screen animation bigger?

This is very complicated.

First, I just checked and the splash screen already uses the entire screen (100vh and 100% width).

However, some users won’t get the splash screen if their Discourse boot is fast enough. For those, then the LCP will be set by whatever element is big enough. In your case the banner, so you are bound by it.

I’d try the prefetch meta tag, and ensure it’s a very well optimized asset and that all your assets are delivered via CDN with PoPs close to where your users are.

4 Likes

Does it have to be?

It’s basically a staple at this point, the users love it

And it can’t be adjusted in any way?

Of course I could always shink it or something but I’d prefer a solution that doesn’t compromise on the aesthetic

I just tried out this possible solution and it didn’t seem to make a difference unfortunately. I also replaced the banner image with a trivially small image as a sanity check and it also made no difference to LCP. Thank you for the suggestion though

I don’t know the inner workings of how theme components get injected into the page but my impression is that by the time the banner component is injected, it’s already too late. My next attempt will be to try it as a plugin.

Far from ideal but this has been working

In case anyone has a similar issue