THE BIG PICTURE TIM KADLEC @TKADLEC

How quickly does the most important content get displayed on the screen?

Largest Contentful Paint (LCP) How long until the largest piece of content is displayed on the screen.

What counts as “content”? <img> - <image> inside svg - post images of <video> elements - background images - block-level text (h1, h2, p, etc) -

LCP Types 18% 27% 46% Mobile 51% Desktop 31% 27% Text Background Image Image

73% 82% on mobile on desktop

LCP at 75th percentile 2.573s 2.861s 3.071s text image background image

LCP at 75th percentile 2.861s 3.071s image background image

LCP at 75th percentile 2.861s 3.071s image background image

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS The browser doesn’t know the image exists until the CSS is downloaded and parsed… CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS …and it won’t request it until it knows it needs to be displayed. CSSOM Render Tree Layout Paint

<link rel=”preload” href=”https://ethicaltraveler.org/ wp-content/themes/et2015/static/images/hero_masthead.jpg” as=”image”/>

Preload helps with discovery, but doesn’t impact priority

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS The browser can find <img> elements pretty early…. CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint …but it doesn’t want those images to delay the more important CSS and JS.

Document: High Scripts: High CSS: High Images: Low Fonts: Medium

Document: Highest Scripts: Normal CSS: Normal Images: Low Fonts: High

Document: Highest Scripts: Low-High CSS: Highest Images: Low-High Fonts: High-Highest

<head> <link rel=”preload” href=”https://ethicaltraveler.org/ wp-content/themes/et2015/static/images/hero_masthead.jpg” as=”image”/>

U … . h o h

Priority Hints to the rescue?

fetchPriority=”high” fetchPriority=”low”

Document: Highest Scripts: Low-High CSS: Highest Images: Low-High Fonts: High-Highest

<link rel=”preload” href=”https://ethicaltraveler.org/ wp-content/themes/et2015/static/images/hero_masthead.jpg” as=”image” fetchpriority=”high”/> </head>

LCP at 75th percentile 2.861s 3.071s image background

/> data-tracking-event=”button-click” data-tracking-info=”{“name”:”clickthrough”,”placeme class=”bose-zoom lazyload” data-lazy=”//assets.bose.com/content/dam/Bose_DAM/Web/consumer_electron alt=”Bose Noise Cancelling Headphones 700” data-sizes=”auto” data-zoom-image=”//assets.bose.com/content/dam/Bose_DAM/Web/consumer_el itemprop=”contentURL” data-srcset=”//assets.bose.com/content/dam/Bose_DAM/Web/consumer_electr

No src?

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint

Network HTML JS DOM CSS CSSOM Render Tree Layout Paint The browser sees an <img>, but with no src, it has no idea what to request until JS is downloaded, parsed and executed.

Markup is your <friend>

From #111 and LCP of ~11.5s

…to #78 and LCP of ~6.3s

<img src=”hero.jpg” loading=”eager”> <img src=”other.jpg” loading=”lazy”>

100 cute kitten photos

Based on viewport

Based on viewport

Based on viewport & connection

4G, 5 total 3G, 8 total 2G, 21 total

Always bet on the browser

//assets.bose.com/content/dam/…

A tale of two domains…

The browser has to establish a connection to the new domain

fi https://res.cloudinary.com /webpagetest /image /upload/ f_auto,q_auto,c_ ll,w_640,h_512/Frame_1_1_dqtcfv.png

Get your buzzword bingo cards ready…..

Get your buzzword bingo cards ready….. Edge Computing

fi https://blog.webpagetest/org/cloudinary /webpagetest /image /upload/ f_auto,q_auto,c_ ll,w_640,h_512/Frame_1_1_dqtcfv.png

// cloudinary.js export default async function (request) { const url = new URL(request.url); url.pathname = url.pathname.substring(11); // remove /cloudinary url.hostname = “res.cloudinary.com”; }; const image = await fetch(url.toString(), request); return image;

// cloudinary.js export default async function (request) { url.pathname = url.pathname.substring(11); // remove /cloudinary url.hostname = “res.cloudinary.com”; };

// cloudinary.js export default async function (request) { return image;

[[edge_functions]] path = “/cloudinary/*” function = “cloudinary.js”

Largest Contentful Paint t n e m e v o r p m i n a e m s m 8 8 4 ~ of

Lots of bandwidth available

Only a couple requests early

fetchPriority=”high” fetchPriority=”low”

<img src=”/images/wpt_home_featureimg.jpg” width=”1414” height=”843” alt=”screenshot of wpt results page” />

<img src=”/images/wpt_home_featureimg.jpg” width=”1414” height=”843” alt=”screenshot of wpt results page” fetchPriority=”high” />

<img

s m 0 4 6 ~ of

First Contentful Paint n o i s s e r g e r n a me s m 5 4 1 ~ f o

3.6s to 2.8s

2.7s to 2.1s

LCP & Images Checklist ⛔ Avoid background images ✅ If you must, then pair background image + preload ✅ Use fetchpriority sparingly for a little boost in Chrome/Edge ✅ Load your LCP images from your main domain ⛔ Don’t lazy-load your LCP image! ✅ Use loading attribute to replace JS lazy-loading

Largest Contentful Paint (LCP)

A more robust measurement Measure image load even when not the LCP Measure in Firefox & Safari

<img class=”post__figure-image” src=”/cloudinary/image/upload/ f_auto,q_auto,c_fill,w_640,h_512/Frame_1_1_dqtcfv.png” srcset=”…” width=”640” height=”512” sizes=”(min-width: 44em) 40rem, 90vw” />

<img class=”post__figure-image” src=”/cloudinary/image/upload/ f_auto,q_auto,c_fill,w_640,h_512/Frame_1_1_dqtcfv.png” srcset=”…” width=”640” height=”512” sizes=”(min-width: 44em) 40rem, 90vw” elementtiming=”post-hero” />

<img elementtiming=”post-hero”

let observer = new PerformanceObserver((list) PerformanceObserver => { for (const entry of list.getEntries()){ console.info(entry); } }); observer.observe({type: type: ‘element’, ‘element’ buffered: true}); true

let observer = new PerformanceObserver((list) => {

Measure in Firefox & Safari

<img src=”hero.jpg” onload=”performance.mark(‘hero1’)”> <script>performance.mark(‘hero2’)</script>

<img class=”post__figure-image” src=”/cloudinary/image/upload/ f_auto,q_auto,c_fill,w_640,h_512/Frame_1_1_dqtcfv.png” srcset=”…” width=”640” height=”512” sizes=”(min-width: 44em) 40rem, 90vw” elementtiming=”post-hero” />

<img class=”post__figure-image” src=”/cloudinary/image/upload/ f_auto,q_auto,c_fill,w_640,h_512/Frame_1_1_dqtcfv.png” srcset=”…” width=”640” height=”512” sizes=”(min-width: 44em) 40rem, 90vw” elementtiming=”post-hero” onload=”performance.mark(‘post-hero2’)” /> <script>performance.mark(‘post-hero3’)</script>

1.377s post-hero3 <script> 1.467s post-hero2 onload

3.542s post-hero3 <script> 5.448s post-hero2 onload

A more robust measurement ✅ Measure image load even when not the LCP ✅ Measure in Firefox & Safari

Why bother?

We’re using metrics supported in two major browsers….

…optimizing for goals (CrUX) based on one browser….

…and measuring with tools that work in one browser.

Meanwhile in Germany…. 47.1% 19.1% 12.8% 6.8%

Web Performance

Remember why we’re doing it….

How easy it to use? Does the functionality meet my needs? Does the functionality work? Is the site fast enough? Is the site available?

“When we design and build our websites with the outliers in mind, whether it’s for performance or even user experience, we build an experience that can be easy for all to access and use — and that’s what the web is about, access and information for all.” - Stephanie Stimac, Program Manager for Edge Developer Experiences

Performance is foundational

THANK YOU! TIM KADLEC @TKADLEC