Quick Wins for Page Speed

Quick Wins for Page Speed

This was a talk at JSFoo 2018. It has a number of things you can do to reduce the time it takes for your page to load, and help reduce the number of users that drop off.

9245f8593dc19a394a021ea0f17f9e0f?s=128

Tejas Dinkar

October 27, 2018
Tweet

Transcript

  1. Quick Wins For Page Speed - Tejas Dinkar, Quintype

  2. Quick Wins For Page Speed - Tejas Dinkar, Quintype Web

    Performance For Lazy People
  3. about.me • Tejas Dinkar • I Make Terrible Jokes, Love

    Clojure + Javascript • Code Monkey - Quintype • @tdinkar • github.com/gja
  4. about.talk • This talk is listed as a beginner ->

    intermediate talk • And I hope to cover a few quick things you can do without rewriting your entire app.
  5. None
  6. None
  7. Why Do We Care? • We power quite a few

    of India’s most popular news sites • The digital news space is competitive, and bounce rates are high! • Many of our readers are from rural parts of the country
  8. None
  9. To reduce drop off 60% of your users will drop

    off in 4 seconds
  10. For SEO As of July 2018

  11. 20% of the Turtles ate 80% of the Pizza Yes,

    it was Michaelangelo Note: Splinter not shown in picture
  12. Not In Scope • gzip / brotli • add caching

    headers and serve from CDN • make your javascript non blocking • use tree shaking to reduce your bundle • React.hydrate instead of React.render
  13. Not In Scope • Make everything a PWA with Isomorphic

    Rendering • Because this was already covered
  14. 7 Step Program for Fast Page Load

  15. 1 - Build A Chart

  16. Here’s One I Made Earlier Trait Browsers For My Project.

    Please Don’t Copy
  17. Here’s One I Made Earlier Trait Browsers The Good Optimize

    for Speed 90% - Last 5 Chrome, Firefox, Edge, Safari, Vivaldi For My Project. Please Don’t Copy
  18. Here’s One I Made Earlier Trait Browsers The Good Optimize

    for Speed 90% - Last 5 Chrome, Firefox, Edge, Safari, Vivaldi The Bad Working, but slow 9% - Chrome 41+, IE11, UC Browser For My Project. Please Don’t Copy
  19. Here’s One I Made Earlier Trait Browsers The Good Optimize

    for Speed 90% - Last 5 Chrome, Firefox, Edge, Safari, Vivaldi The Bad Working, but slow 9% - Chrome 41+, IE11, UC Browser The Ugly Don’t bother IE 6-10 For My Project. Please Don’t Copy
  20. Be ruthless in your browser compatibility matrix

  21. 2 - Profile

  22. 3 - Profile

  23. 4 - Profile

  24. 5 - Profile

  25. 6 - Make a small change

  26. 7 - Profile

  27. Profiling - Why? To protect you from the speaker of

    today’s talk convincing you that there are silver bullets out there!
  28. Fast pages are all alike; every slow page is slow

    in its own way ~ Leo Tolstoy (probably)
  29. Profiling - What? • First Page Load v/s Subsequent Load

    • Time to first Contentful paint v/s Time to Interactive • Above the fold v/s the full page
  30. None
  31. Profiling - How?

  32. None
  33. Profiling - How?

  34. Profiling - How?

  35. Profiling - How?

  36. Serving large asset with gzip is fine

  37. Compression reduces network, but not processing

  38. Sometimes the best Javascript is No Javascript

  39. On To Some Quick Wins

  40. Tell the browser what’s important 1: <html> 2: ... 3:

    <link rel="stylesheet" href="/some.css"/> 4: ...
  41. Tell the browser what’s important 1: <html> 2: ... 3:

    <link rel="stylesheet" href="/some.css"/> 4: ... 5: <img src="this-is-below-the-fold.png"/> 6: ...
  42. Tell the browser what’s important 1: <html> 2: ... 3:

    <link rel="stylesheet" href="/some.css"/> 4: ... 5: <img src="this-is-below-the-fold.png"/> 6: ... 7: <script src="bloated-third-party.js"></script> 8: <script src="/assets/app.js"></script> 9: </html>
  43. Your browser is smart, but you can make it smarter!

  44. Link Headers and H/2 push • Link: </assets/app.js>; rel=preload; as=script

    • Link: </user.json>; rel=preload; as=fetch • <link rel="dns-prefetch" href="//example.com"> • <link rel="preconnect" href="//example.com"> • CDNs will convert this to an HTTP2 push if it can • Fastly + Cloudflare both support this • Be careful, HTTP/2 is a mixed bag
  45. Inline Your CSS • For years I’ve searched for the

    perfect way to do critical CSS • Then, I tried to just embed the entire CSS in page • In particular, this can improve your first time page load • If using a CSP header, remember to sign your CSS
  46. None
  47. CSS Modules • Delete unused CSS • Note: Not css-in-js

    • Remember to chunk your CSS /* header.css */ .logo { display: block; } // header.js import styles from './header.css' function Logo() { return <img className={styles["logo"]} />; }
  48. Shrink Your CSS • Chrome has a ‘Coverage’ Tab, hidden

    way down there • Our target is to get Unused CSS down to 30%
  49. Font CSS • Make sure you use woff2 (and fallback)

    • Choose your FOUT / FOIT strategy • Font-Display is your BFF! • But can cause multiple renders. Use fontfaceobserver.js for multiple fonts • Don’t use a different CSS for your fonts
  50. None
  51. None
  52. Images • Images are the single largest contributor to weight

    (~55%) • Serve the right image size for the right device. Use imgix, ImageKit, thumbor, CF Polish, etc…. • And use the right format too! web-p! • Use img srcset to serve the multiple devices with a single HTML tag <img src="/img.jpg" srcset="/small.jpg 200w, /medium.jpg 400w" sizes="(min-width: 1024px) 25vw, 100vw" />
  53. Lazy Load Images • Lazy Load images below the fold

    • Do NOT LazyLoad anything above the fold • Use IntersectionObserver… It’s super easy!
  54. Lazy Load Images 1: <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///AEA..." 2: data-src="https://www.google.com/images/branding/go..." /> 3:

  55. Lazy Load Images 1: <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///AEA..." 2: data-src="https://www.google.com/images/branding/go..." /> 3:

    4: <script type="text/javascript"> 5: const observer = new IntersectionObserver(showImage, { 6: margin: '20px 0 100px', 7: });
  56. Lazy Load Images 1: <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///AEA..." 2: data-src="https://www.google.com/images/branding/go..." /> 3:

    4: <script type="text/javascript"> 5: const observer = new IntersectionObserver(showImage, { 6: margin: '20px 0 100px', 7: }); 8: 9: document.querySelectorAll("img[data-src]") 10: .forEach(x => observer.observe(x)) 11:
  57. Lazy Load Images 1: <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///AEA..." 2: data-src="https://www.google.com/images/branding/go..." /> 3:

    4: <script type="text/javascript"> 5: const observer = new IntersectionObserver(showImage, { 6: margin: '20px 0 100px', 7: }); 8: 9: document.querySelectorAll("img[data-src]") 10: .forEach(x => observer.observe(x)) 11: 12: function showImage(entries, observer) { 13: entries.forEach(entry => { 14: if(entry.isIntersecting) 15: entry.target.src = entry.target.getAttribute("data-src") 16: }) 17: } 18: </script>
  58. POLYFILLS

  59. POLYFILLS

  60. Polyfills

  61. Conditional Polyfills import 'promise-polyfill'; import "babel-polyfill"; import 'whatwg-fetch';

  62. Conditional Polyfills import 'promise-polyfill'; import "babel-polyfill"; import 'whatwg-fetch';

  63. Conditional Polyfills 1: <script> 2: if(!window.fetch || !window.Array.prototype.map || !window.Promise)

    { 3: s = document.createElement('script'); 4: s.type = 'text/javascript'; 5: s.src = '/path/to/polyfill.js'; 6: document.getElementsByTagName('head')[0].appendChild(s); 7: } 8: </script> 9: 10: <script src="/path/to/app.js"></script>
  64. This Chart Again Trait Browsers The Good Optimize for Speed

    90% - Last 5 Chrome, Firefox, Edge, Safari, Vivaldi The Bad Working, but slow 9% - Chrome 41+, IE11, UC Browser The Ugly Don’t bother IE 6-10
  65. Every time you add a library, check your bundle size

  66. Wasting 16MB of RAM Across 225k Concurrent Users Wastes 3.6TB

    of RAM Globally!
  67. Speaking of Libraries Let’s Play A Game

  68. When I say It’s 2018 You Say You don’t need

    it anymore
  69. lodash

  70. Lodash _.map(array, a => a + 1) | v array.map(a

    => a + 1)
  71. Lodash 1: // Don't do this 2: import _ from

    'lodash'; 3: _.omit(myObject, 'lodash');
  72. Lodash 1: // Don't do this 2: import _ from

    'lodash'; 3: _.omit(myObject, 'lodash'); 4: 5: // Use a totally different, tiny library 6: import omit from 'object.omit'; 7: omit(myObject, 'lodash');
  73. Lodash 1: // Don't do this 2: import _ from

    'lodash'; 3: _.omit(myObject, 'lodash'); 4: 5: // Use a totally different, tiny library 6: import omit from 'object.omit'; 7: omit(myObject, 'lodash'); 8: 9: // Just import the right bit of lodash 10: import omit from 'lodash/omit'; 11: omit(myObject, 'lodash');
  74. Lodash 1: // Don't do this 2: import _ from

    'lodash'; 3: _.omit(myObject, 'lodash'); 4: 5: // Use a totally different, tiny library 6: import omit from 'object.omit'; 7: omit(myObject, 'lodash'); 8: 9: // Just import the right bit of lodash 10: import omit from 'lodash/omit'; 11: omit(myObject, 'lodash'); 12: 13: // Works with Webpack 4 14: import {omit} from 'lodash'; 15: omit(myObject, 'lodash');
  75. jQuery especially the selector and bind bits

  76. jQuery 1: const masthead = $(".masthead a"); 2: 3: masthead.bind("click",

    function(e) { 4: e.preventDefault() 5: 6: masthead.addClass("loading"); 7: masthead.hide(500); 8: 9: $.getJson("/foo.json", () => masthead.removeClass("loading")) 10: })
  77. !jQuery 1: const masthead = document.querySelector(".masthead a"); 2: 3: masthead.addEventListener("click",

    function(e) { 4: e.preventDefault(); 5: 6: masthead.classList.add("loading"); 7: masthead.style.height = 0; // Use CSS Transition to shrink 8: 9: fetch("/foo.json") 10: .then(res => res.json()) 11: .then(() => masthead.classList.remove("loading")); 12: })
  78. jQuery - caveats • $(‘.x’).bind is on element, use querySelectorAll.forEach()

    • loads of caveats on querySelectorAll • fetch works slightly differently from XHR • jQuery supports more browsers than if you write it yourself :-p
  79. moment and other libraries that come loaded with locales

  80. Final Thoughts • Though we were already a PWA, that

    wasn’t nearly enough to get us to a sub second page load • We actually brought our FCP down from 3500 to 900 ms, and saw a 35% increase in traffic • This is a continuous journey. Perf day should be every day • Measure your important metrics as part of your CI
  81. Links • Chrome Dev Podcast - HTTP 203 https:// developers.google.com/web/shows/http203/

    • https://chrisbateman.github.io/webpack-visualizer/ • https://cssstats.com
  82. ` • Twitter - @tdinkar • tejas@gja.in • github.com/gja