1 of 45

HTML Optimization for

Web Perfomance

Frontend Conference Fukuoka 2019

Shogo SENSUI (@1000ch)

2 of 45

3 of 45

Shogo SENSUI (@1000ch)

  • Software Engineer focusing on the web technology
  • Engineering Manager and Tech Lead at Merpay, Inc.
  • This is the second in Fukuoka. My first time was 2014.01.25

4 of 45

Why HTML Optimization?

5 of 45

HTML is the first step of rendering a page

6 of 45

The origin of

subresources

Loading order affects rendering speed directly

7 of 45

Independent of

web page type

Regardless of dynamic web application or not

8 of 45

Easy to optimize

relatively

Mostly completing in HTML

9 of 45

3 steps for HTML Optimization

  1. Understanding Critical Rendering Path
  2. Measuring Performance Metrics
  3. Optimizing HTML

10 of 45

Understanding

Critical Rendering Path

11 of 45

Browser Loading Process

  1. Download HTML
    1. Parse HTML to construct DOM
  2. Download subresources
    • Parse CSS to construct CSSOM
    • Parse and evaluate JavaScript synchronously
    • Parse images, audios, videos, etc.
  3. Create Render Tree using DOM and CSSOM
    • Wait for DOM and CSSOM
    • Layout nodes
    • Paint nodes

Construct

DOM Tree

Construct

CSSOM Tree

Create

Render Tree

Layout & Paint

Execute

JavaScript

12 of 45

1st step. Load HTML

  • Download HTML from requested URL
  • Parse HTML to construct DOM tree
  • HTML will be evaluated from the top

<html>

<head>

<meta charset="utf-8">

<title>Basic HTML structure</title>

<link rel="stylesheet" href="foo.css">

<link rel="stylesheet" href="bar.css">

</head>

<body>

<img src="hero.jpg">

<script src="app.js"></script>

<script defer src="3rd-party.js"></script>

</body>

</html>

13 of 45

2nd step. Load subresources

  • Download subresources specified in HTML elements
    • <link>
    • <script>
    • <img>
    • <iframe>
    • etc…
  • CSS will be downloaded and evaluated asynchronously
  • JavaScript will be downloaded and evaluated synchronously
    • will affect DOM and CSSOM

<html>

<head>

<meta charset="utf-8">

<title>Basic HTML structure</title>

<link rel="stylesheet" href="foo.css">

<link rel="stylesheet" href="bar.css">

</head>

<body>

<img src="hero.jpg">

<script src="app.js"></script>

<script defer src="3rd-party.js"></script>

</body>

</html>

14 of 45

3rd step. Render pages

  • Wait for DOM and CSSOM are completely parsed
  • Create Render Tree using DOM and CSSOM
  • Display page
    • Layout nodes
    • Paint nodes

without CSSOM

with CSSOM

15 of 45

Measuring

Performance Metrics

16 of 45

Wait, Load and DOMContentLoaded events?

  • They are not user-centralized performance metrics
  • We want to score the real-user experience

17 of 45

Which experience would you prefer?

18 of 45

Time to Interactive Demo (Airbnb mobile web)

19 of 45

User Centric Performance Metrics

20 of 45

21 of 45

  • First Paint: When anything on the first view is visible to the users
  • First Contentful Paint: When any content on the first view is visible to the users

22 of 45

  • Timing when the biggest content on the first view was painted
  • These elements are considered
    • <img>, <image> in <svg>, <video>
    • An element with a background image loaded via CSS url()
    • Block-level elements

23 of 45

  • The process make the main thread busy
  • Long Tasks will cause the UI to freeze
    • Delay time-to-interactive
    • Input latency

24 of 45

  • How long it takes a page to become fully interactive
  • Use GoogleChromeLabs/tti-polyfill to measure
    • It requires Long Tasks API is supported

25 of 45

  • Enables you to measure the web application performance on the user’s device

const po = new PerformanceObserver(list => {

for (const entry of list.getEntries()) {

// `entry` is a PerformanceEntry instance.

console.log(entry.entryType);

console.log(entry.startTime);

console.log(entry.duration);

}

});

// Start observing the entry types you care about.

po.observe({

entryTypes: ['resource', 'paint']

});

26 of 45

To measure FP, FCP, LCP:

const observer = new PerformanceObserver(list => {

for (const entry of list.getEntries()) {

ga('send', 'event', {

eventCategory: 'Performance Metrics',

eventAction: entry.name,

eventValue: entry.startTime + entry.duration,

nonInteraction: true

});

}

});

// Start observing the entry types, FP, FCP, LCP.

observer.observe({

entryTypes: ['paint', 'largest-contentful-paint']

});

27 of 45

To measure LongTasks:

const observer = new PerformanceObserver(list => {

for (const entry of list.getEntries()) {

ga('send', 'event', {

eventCategory: 'Performance Metrics',

eventAction: 'longtask',

eventValue: Math.round(entry.startTime + entry.duration),

eventLabel: JSON.stringify(entry.attribution),

});

}

});

// Start observing the entry types, Long Task.

observer.observe({

entryTypes: ['longtask']

});

28 of 45

Performance Audit Tools

29 of 45

30 of 45

31 of 45

How to optimize HTML?

32 of 45

All (sub-)resources should be minified

33 of 45

Optimize CSS Loading

  1. Put <link rel=stylesheet> in <head>
    1. To prefer loading CSS and constructing CSSOM
    2. Putting multiple <link>s is OK! Because browser will load them asynchronously
  2. Do not have to concatenate CSS files
    • Especially in http/2 era
    • To enable cache effectively
    • To minimize build process

34 of 45

Optimize JavaScript Loading

  • Put <script></script> at the end of <body>
    • To prefer constructing HTML
    • To prefer loading subresources
  • Add defer attribute to <script> that loads 3rd party JavaScript
    • To prefer rendering the current web page

35 of 45

Basic HTML structure will be...

<html>

<head>

<meta charset="utf-8">

<title>Basic HTML structure</title>

<link rel="stylesheet" href="foo.css">

<link rel="stylesheet" href="bar.css">

</head>

<body>

<!-- ... -->

<script src="app.js"></script>

<script defer src="3rd-party.js"></script>

</body>

</html>

36 of 45

Preload subresources

  • Load subresources actively
  • Specify preload attribute for <link>
  • Preload directive: as attribute to specify the kind of subresource
    • as=media: Audio, Video
    • as=script: Script file
    • as=style: CSS file
    • as=font: Font file
    • as=image: Image file
    • etc...

<link rel="preload" href="audio.mp3" as="media">

<link rel="preload" href="app.css" as="style">

<link rel="preload" href="app.js" as="script">

<link rel="preload" href="hero.jpg" as="image">

37 of 45

Preload for module scripts

  • Load module scripts actively
  • Specify modulepreload attribute for <link>
  • Further information

<head>

<link rel="modulepreload" href="app.mjs">

</head>

<!-- ... -->

<script type="module" src="app.mjs"></script>

38 of 45

Native lazy-loading for <img> and <iframe>

  • loading attribute to defer the loading of off-screen images and iframes
    • <img>
    • <iframe>
  • Don’t have to use IntersectionObserver and to observe scroll and resize events.

<img src="image.png" loading="lazy" alt="…">

<iframe src="https://example.com" loading="lazy"></iframe>

39 of 45

Priority Hints for subresources

  • importance attribute to suggest the resource priority
    • <link>
    • <img>
    • <script>
    • <iframe>

<!-- An image the browser assigns "High" priority, but we don't actually want that. -->

<img src="in_viewport_but_not_important.svg" importance="low" alt="...">

<!-- We want to initiate an early fetch for a resource, but also deprioritize it -->

<link rel="preload" href="/js/script.js" as="script" importance="low">

<script src="/js/app.js" defer importance="high"></script>

40 of 45

Resource Hints

  • Load subresources speculatively
  • DNS Prefetch
    • Resolves DNS
  • Preconnect
    • Creates TCP connection
  • Prefetch
    • Fetches resources
  • Prerender
    • Renders HTML page

<link rel="dns-prefetch" href="//example.com">

<link rel="preconnect" href="//example.com">

<link rel="preconnect" href="//cdn.example.com" crossorigin>

<link rel="prefetch" href="next-page.html" as="document">

<link rel="prefetch" href="lib.js" as="script">

<link rel="prerender" href="next-page.html">

41 of 45

  • Faster subsequent page-loads by prefetching in-viewport links during idle time

42 of 45

Conclusion

43 of 45

Conclusion

  • Understanding Critical Rendering Path
    1. Browsers wait for DOM and CSSOM are parsed before rendering
  • Measuring Performance Metrics
    • Use WebPageTest and Lighthouse to audit the web page
    • Audit the web page continuously
  • Optimizing HTML
    • For initial loading and runtime
    • Minimizing payload is the premise for both

44 of 45

Further information?

  • 基礎知識と実践的なノウハウを体系的に解説し、Web パフォーマンスに関する本質的な理解を促します
  • 詳しくは https://webperf.guide を御覧ください

45 of 45

Thank for listening 🙌

Ask me anything ❤️ by @1000ch