Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Lessons Learned Sciencing The Web

Addy Osmani
November 11, 2017

Lessons Learned Sciencing The Web

My talk from FFConf 2017.

Addy Osmani

November 11, 2017
Tweet

More Decks by Addy Osmani

Other Decks in Technology

Transcript

  1. DON’T BE BIG ✂ Code-split your JavaScript Compress resources ⚡

    Minify & optimize *.* Tree-shake modules Respect data plans Don’t over-do Web Fonts ONLY LOAD WHAT YOU NEED Lazy-load non-critical resources Preconnect to important origins Preload critical resources Minimize redirects & round-trips ONLY LOAD WHAT CHANGED Cache resources effectively Be network resilient with Service Workers LOADING BEST PRACTICES
  2. Time to Interactive <5s on an average mobile device over

    3G *2s on repeat-load a:er Service Worker registered goal
  3. Chrome DevTools Lighthouse WebPageTest TOOLS TO SCIENCE THE WEB Synthetic

    lab conditions Real-world RUM Synthetic health of the web Puppeteer ~500K sites BETA
  4. Origin Form Factor Effective Connection Type (e.g 3G, 4G) First

    Paint First ContentFul Paint domContentLoaded onLoad
  5. // Network type that browser uses
 navigator.connection.type > 'wifi' 


    // New: Effective connection type // using rtt and downlink values
 navigator.connection.effectiveType > '2G' I want to adapt serving based on estimated network quality BEFORE AFTER For more on navigator.connection.* See ‘Building a modern media experience’ Chrome 62
  6. “Networks, CPUs and disks all hate you. On the client,

    you pay for what you send in ways you can't easily see” - Alex Russell, Chrome
  7. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU,

    Cable (5/1mbps, 28ms) STATE OF JAVASCRIPT ON MOBILE 1MB 600KB+ 300KB+ 10% sites 25% sites 50% sites
  8. Where do mobile sites spend their time loading? With thanks

    to Camillo and Mathias @ V8 Average Housing Forbes Treebo Twitter Trivago Lancome Tech Today OLACabs Wego Konga
  9. 40% SITES MAY USE ONLY OF THE JAVASCRIPT THEY LOAD

    UPFRONT. With thanks to [email protected] JS CODE COVERAGE OF TOP 50 SITES
  10. Code-splitting // Defines a “split-point” for a separate bundle require.ensure([],

    () => { const profile = require('./UserProfile', cb); }); import('./UserProfile') .then(loadRoute(cb)) .catch(errorLoading) Webpack 2+ Webpack 1 Also see Splittable, Closure Compiler or Browserify
  11. Minify _everything_ Babelified ES5 w/Uglify ES2015+ with babel-minify css-loader +

    minimize:true Code-splitting Dynamic import() Route-based chunking Tree-shaking Webpack 2+ with Uglify RollUp DCE w/ Closure Compiler Optimize “Vendor” libs NODE_ENV=production CommonsChunk + HashedModuleIdsPlugin() Transpile less code babel-preset-env + modules:false Browserlist useBuiltIns: true Scope Hoisting: Webpack 3 RollUp Strip unused Lodash modules lodash-webpack-plugin babel-plugin-lodash Fewer Moment.js locales ContextReplacementPlugin()
  12. Pinterest’s old Mobile Site - 1st load First Paint: 4.2s

    First Meaningful Paint: 6.2s Time To Interactive: 23s
  13. Pinterest’s new Mobile Site - 1st load First Paint: 1.8s

    First Meaningful Paint: 5.1s Time To Interactive: 5.6s JS Bundles: 620KB ➡ 150KB CSS Bundles: 150KB ➡ 6KB inline P90 for Pin pages: 20s ➡ 6.5s
  14. Webpack Bundle Analyzer: After moving out common code from async

    chunks into entryChunk 60-90% decrease in size of async route chunks (e.g 13.9KB ➡ 1KB) 20% increase in size of entry (59KB ➡ 71KB)
  15. Performance Budgets for JS Budgets Tinder tries not to exceed

    Vendor Async Other 155KB 55KB 35KB CSS 20KB
  16. import A from '../A';
 import B from '../B'; 
 const

    route = [
 {
 route: '/',
 regions: {
 side: A,
 main: B
 }
 }
 ]; JavaScript Route-based code-splitting Before main.js A B
  17. import Loadable from ‘react-loadable'; 
 const A = Loadable({
 loader:

    () => import('../A' /* webpackChunkName: "pc-r-A" */),
 loading: () => null
 }); 
 const B = Loadable({
 loader: () => import('../B' /* webpackChunkName: "pc-r-B" */),
 loading: () => null
 });
 
 const route = [
 {
 route: '/',
 regions: {
 side: A,
 main: B
 },
 preload: [ /* next page chunk to preload*/ ]
 } React Loadable CommonsChunkPlugin After A.js B.js
  18. JavaScript Route-based code-splitting + budgets Before Main bundle size: 166kb

    DOMContentLoad: 5.46s load: 11.91s After Main bundle size: 101kb DOMContentLoad: 4.69s load: 4.69s
  19. * Chrome has 4+ caches. The above reflects the main

    two - the HTTP and memory caches Chrome’s Cache Hit Rates
  20. Cache-Control Policies /page HTML /style.3da37df.css CSS /script.8sd34ff.js JavaScript /photo.jpg Image

    Cache-Control: max-age=31536000 Cache-Control: no-cache Cache-Control: private, max-age=31536000 Cache-Control: max-age=86400 https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching
  21. HTTP Caching Checklist Use consistent URLs and minimize resource churn

    Provide a validation token (ETag) to avoid transferring unchanged bytes Identify resources that can be cached by intermediaries (like CDNs) Determine the optimal cache lifetime of resources (max-age) Consider a Service Worker for more control over your repeat visit caching 1. 2. 3. 4. 5. bit.ly/caching-checklist
  22. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU,

    Cable (5/1mbps, 28ms) TIME TO INTERACTIVE ON MOBILE 35s 22s 14s 10% sites 25% sites 50% sites
  23. <link rel="preload" href="movies.json" as="fetch" crossorigin="use-credentials">
 <script>
 (async () => {


    try {
 const response = await fetch(new Request("movies.json", {credentials: "include"}));
 const data = await response.json();
 console.log(data);
 } catch (exception) {
 console.log("Booo");
 }
 })();
 </script> I have critical resources I want to load earlier than discovery. Chrome 62
  24. Express + HTTP/2 Push Headers const express = require('express'), let

    app = express(); app .use('/js', express.static('js')) .get('/', function (req, res) { res.set('Link', ` </style.css>; rel=preload; as='style', </js/vendor.bundle.js>; rel=preload; as='script', </js/app.bundle.js>; rel=preload; as='script'`)
  25. <5s

  26. AUTOMATE https://github.com/addyosmani/critical INLINE CRITICAL CSS Critical - extract & inline

    critical CSS using Chrome Headless <head> <style> /* critical */ </style> </head> <script> /* loadCSS */ </script> inline async
  27. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU,

    Cable (5/1mbps, 28ms) WEB PAGE WEIGHT ON MOBILE 5.4MB 2.9MB 1.4MB 10% sites 25% sites 50% sites
  28. Apache NGINX gzip on; gzip_vary on; gzip_comp_level 6; gzip_http_version 1.1;

    gzip_proxied any; gzip_min_length 256; gzip_buffers 16 8k; gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript; AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/xml AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType DEFLATE application/xhtml+xml AddOutputFilterByType DEFLATE application/rss+xml AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/x-javascript .htaccess nginx.conf
  29. Brotli Improved load time by 7% in India & 4%

    U.S bit.ly/linkedin-brotli Decreased the size of static assets by 20% bit.ly/dropbox-brotli 17% improvement for largest JS bundles bit.ly/certsimple-brotli 1.5 petabytes (million gigs) saved a day bit.ly/playstore-brotli
  30. Brotli Use: Mostly JavaScript, CSS and HTML From “Tracking the

    performance of the web with HTTP Archive”
  31. Remove unnecessary downloads The fastest and best-optimized resource is a

    resource not sent. Inventory your own assets and third-party assets on your pages. Measure the perf of each asset: its value and its technical performance. Determine if the resources are providing sufficient value. Lazy-load/defer resources that are non-critical as much as possible. 1. 2. 3. 4. 5.
  32. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU,

    Cable (5/1mbps, 28ms) WEB FONT WEIGHT ON MOBILE 200KB 100KB 80KB 10% sites 25% sites 50% sites
  33. @font-face {
 font-family: 'Roboto';
 font-display: optional;
 src: url(Roboto.woff) format('woff'),
 url(Roboto.eot)

    format('eot');
 font-weight: 400;
 font-style: normal;
 } If my Web Fonts can’t load quickly, don’t load them at all. Chrome 60 Safari WIP Firefox WIP
  34. Use System Fonts when you can The fastest font is

    one that doesn’t need to load. Try font-display: optional; If a Web Font can’t load fast, load a fallback instead. If the Web Font is cached, it’ll get used the next time the user loads the page. Try <link rel=preload as=font> to request Web Fonts with a higher priority If Web Fonts are a critical to your UX, preload them to minimize FOIT. Try subsetting to limit the range of Web Font characters needed Subsetting removes characters & Open-Type features from fonts, reducing file size. Google Fonts, TypeKit & Font Squirrel support it. Be careful with use. Try the CSS Font Loading API if you need more control Track font download progress & apply once fetched, manipulate font faces and override default lazy load behavior. S Have a Web Font Loading Strategy @addyosmani
  35. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU,

    Cable (5/1mbps, 28ms) IMAGE WEIGHT ON MOBILE 3.9MB 1.9MB 0.8MB 10% sites 25% sites 50% sites
  36. Adapt intelligently H E I G H T Size appropriately

    WIDTH IMAGE DECODE Compress carefully Take care with tools Prioritize critical images HIGH LOW Lazy-load the rest Choose the right format Image Optimisation
  37. Data Saver Mode introduced up to 70% savings for Twitter

    Lite Do this with the browser using the Save-Data client hint
  38. 5s