Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Tejas Dinkar

October 27, 2018
Tweet

More Decks by Tejas Dinkar

Other Decks in Technology

Transcript

  1. about.me • Tejas Dinkar • I Make Terrible Jokes, Love

    Clojure + Javascript • Code Monkey - Quintype • @tdinkar • github.com/gja
  2. 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.
  3. 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
  4. 20% of the Turtles ate 80% of the Pizza Yes,

    it was Michaelangelo Note: Splinter not shown in picture
  5. 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
  6. Not In Scope • Make everything a PWA with Isomorphic

    Rendering • Because this was already covered
  7. 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
  8. 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
  9. 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
  10. Profiling - Why? To protect you from the speaker of

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

    in its own way ~ Leo Tolstoy (probably)
  12. 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
  13. Tell the browser what’s important 1: <html> 2: ... 3:

    <link rel="stylesheet" href="/some.css"/> 4: ...
  14. 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: ...
  15. 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>
  16. 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
  17. 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
  18. 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"]} />; }
  19. Shrink Your CSS • Chrome has a ‘Coverage’ Tab, hidden

    way down there • Our target is to get Unused CSS down to 30%
  20. 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
  21. 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" />
  22. Lazy Load Images • Lazy Load images below the fold

    • Do NOT LazyLoad anything above the fold • Use IntersectionObserver… It’s super easy!
  23. 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: });
  24. 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:
  25. 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>
  26. 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>
  27. 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
  28. Lodash 1: // Don't do this 2: import _ from

    'lodash'; 3: _.omit(myObject, 'lodash');
  29. 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');
  30. 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');
  31. 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');
  32. 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: })
  33. !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: })
  34. 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
  35. 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
  36. Links • Chrome Dev Podcast - HTTP 203 https:// developers.google.com/web/shows/http203/

    • https://chrisbateman.github.io/webpack-visualizer/ • https://cssstats.com