Extreme Web Performance Optimization

Who Are We?

  1. we build web apps
  2. we build web developers
  3. we go out to work with others
    to get better at 1 and 2

Disclaimer

Young in the ways of WPO

How important is WPO?

Conversions

The reason why we build stuff

store => orders

blog => read articles

app => interact & engage

40%

of users abandon an article if it doesn't load after six seconds

Danny Bernstein, Google

In terms we can understand

load_time > 6000 && 
Math.random() < .4 ?
fail() : profit()

500ms delay

+26% user’s frustration

Source: Radware

In terms we can understand

while( wait_time % 500 )

    (╯°□°)╯︵ ┻━┻ 

500ms delay

-20% Google’s traffic

http://glinden.blogspot.com/2006/11/marissa-mayer-at-web-20.html

+100ms delay

-1% Amazon’s sales

http://glinden.blogspot.com/2006/11/marissa-mayer-at-web-20.html

$1070000000

2015

$1,070,000,000

2015

-2.2s page load

+15% downloads

Source: Firefox

+60% faster

+14% donations

Source: Obama Campaign

- The Google

we’ve decided to take site speed into account in our search rankings

https://webmasters.googleblog.com/2010/04/using-site-speed-in-web-search-ranking.html

My sites are fast enough...

- no one ever

What even is page speed?

Page Load Time

TTFB

TTG

https://blog.cloudflare.com/ttfb-time-to-first-byte-considered-meaningles

how long it takes for a webpage to be loaded and usable by a browser

https://www.littlebizzy.com/blog/ttfb-meaningless

ATF

User satisfaction metrics are based on Perception, ATF

First Tool

www.webpagetest.org

Waterfall charts and HAR

Filmstrip View

Moar Tools

Visual Progress chart

Where to optimize?

Optimizing
on the server

50% optimization

~ 10% of final impact

It's not a simple task

Optimizing
on the client

50% optimization

~ 45% of final impact

is simple

immediate results

Perception

vs.

Performance

Performance

Perception

Performance

Perception

Usability Engineering 101

Delay User reaction
0 - 100 ms Instant
100 - 300 ms Feels sluggish
300 - 1000 ms Acceptable for changing views
> 1 s Mental context switch
> 10 s Frustrated, leave,
may or may not return

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail

RAIL

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail

  • Respond to user input within 100ms
  • Else
    • connection between action and reaction is broken, "laggy"
    • includes click, tap, scroll, swipe
  • Do not block the user, do expensive work in the background (if possible)

https://developers.google.com/web/fundamentals/performance/rendering/

  • Render frames every 16ms
  • Includes scrolling, swiping
  • Every frame must
    JS > Style > Layout > Paint > Composite
  • Each frame has 16.66ms
  • You really have 10ms
  • Within the 100ms time, precalculate if you can

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail

  • Maximize idle time
  • Use idle time to complete deferred work
  • Load BTF content
  • Group deferred work % 50 ms
    • If user interacts, response is > priority
    • Ensure instant response

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail

  • Load ATF in < 1 second
  • Else
    • attention loss
    • broken context
  • Optimize the critical rendering path
    • unblock rendering

#WebPerf

optimize-fu

Rule #1: User First

If it's a "Dirty Hack"

and it makes UX better   

Do it!

Don't make it so pretty that it doesn't work for the end user

Devs must remember...

Do not block the critical rendering path

...what happens in the intermediate steps between receiving the HTML, CSS, and JavaScript bytes and the required processing to turn them into rendered pixels

Allow progressive rendering

load html > load resources > parse > display

https://developers.google.com/web/fundamentals/performance/critical-rendering-path

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/optimizing-critical-rendering-path

https://varvy.com/pagespeed/critical-render-path.html

Web fonts turn

 

Into a

Non-Blocking text

Blocking Resource

https://css-tricks.com/fout-foit-foft

JS blocks parsing HTML

Worse than blocking rendering

</body>

Inline CSS

Renders faster

<div style="color:salmon">
require('juice')
require('inline-css')

Cache Policy should be set by the Content Decider

Not DevOps

proxy_cache_path /var/lib/nginx/cache  levels=1:2 keys_zone=backcache:8m max_size=50m;
proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;

location / {
    proxy_cache backcache;
    proxy_cache_bypass  $http_cache_control;
    add_header X-Proxy-Cache $upstream_cache_status;
...

- from the Google webmaster guidelines

Make sure that your web server correctly supports the

If-Modified-Since HTTP header.

The If-Modified-Since header is sent to a server as a conditional request.

if ( contentChanged )
  respond.status(200).send(content);
else
  respond.status(304);

https://varvy.com/ifmodified.html

Use Service Workers as a proxy on the client

Create your own Cache Policy

http://www.html5rocks.com/en/tutorials/service-worker/introduction/

Register a serviceWorker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(function(registration) {
      console.log(
        'ServiceWorker registered with scope: ',
        registration.scope
      );
  }).catch(function(err) {
    console.log(
      'ServiceWorker registration failed: ',
      err
    );
  });
}

chrome://inspect/#service-workers

Open Cache, Cache urls

var CACHE_NAME = 'my-app-cache-v1';
var urlsToCache = [
  '/',
  '/css/bundle.css',
  '/js/bundle.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

Cache Hit, or return Request

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        return fetch(event.request);
      }
    )
  );
});

There's a proxy in your browser

https://docs.google.com/presentation/d/1GNLc4oRZzazq4Th8vsH3v5GekAbKWsxIXHbNtQFFG-c/present

Set static content to expire late in the future

<img src="logo.png" />
<img src="logo.242.png" />
<img src="logo.png#20160831" />

gzip all the plaintext

gzip            on;
gzip_min_length 1000;
gzip_proxied    expired 
                no-cache 
                no-store 
                private 
                auth;
gzip_types      text/plain 
                application/xml
                application/json;
Accept-Encoding: gzip, deflate
Content-Encoding: gzip

zopfli all the plaintext

# Enable Nginx HttpGzipStaticModule
server {
  ...
  gzip_static on;
}
Accept-Encoding: gzip, deflate
Content-Encoding: gzip
  • compression only
  • use existing methods to decompress
  • good for (pre) compressing once
  • very slow, not good for on the fly compression
  • uncompressed: 106,272 bytes

  • mod_deflate: 28,732 bytes (27%)

  • gzip -9: 28,062 bytes (26%)

  • Zopfli: 27,033 bytes (25%)

https://github.com/google/zopfli
http://www.cambus.net/serving-precompressed-content-with-nginx-and-zopfli/
https://www.stevesouders.com/blog/2013/03/08/zopflinator/

brotli all the plaintext

server {
  ...
  brotli on;
  brotli_static on;
  brotli_types  text/plain application/xml application/json;
}
Accept-Encoding: brotli

https://github.com/google/ngx_brotli
https://quixdb.github.io/squash-benchmark/

ngx_brotli filter module - on the fly

ngx_brotli static module - precompressed

comp level comp speed decomp speed
zlib deflate 4 21.45 MiB/s 96.16 MiB/s
zlib gzip 4 20.71 MiB/s 89.21 MiB/s
brotli 1 23.94 MiB/s 115.02 MiB/s

Browser opens

6 lanes per origin

exploit that

https://www.devleague.com/css/bundle.css

origin

:443

https://www.devleague.com/css/bundle.css

https://www.devleague.com/js/bundle.js

https://www.devleague.com/images/asset1.png

https://www.devleague.com/images/asset2.png

https://www.devleague.com/images/asset3.png

https://www.devleague.com/images/asset4.png

https://www.devleague.com/images/asset5.png

https://www.devleague.com/images/asset6.png

https://www.devleague.com/images/asset7.png

https://www.devleague.com/images/asset8.png

https://www.devleague.com/images/asset9.png

https://www.devleague.com/images/asset10.png

https://www.devleague.com/images/asset11.png

https://www.devleague.com/images/asset12.png

https://www.devleague.com/images/asset13.png

https://www.devleague.com/images/asset14.png

https://www.devleague.com/images/asset15.png

https://www.devleague.com/images/asset16.png

Waiting...

Loading...

https://cdn1.devleague.com/css/bundle.css

origin

:443

https://cdn1.devleague.com/css/bundle.css

https://cdn1.devleague.com/js/bundle.js

https://cdn1.devleague.com/images/asset1.png

https://cdn1.devleague.com/images/asset2.png

https://cdn1.devleague.com/images/asset3.png

https://cdn1.devleague.com/images/asset4.png

https://cdn2.devleague.com/images/asset5.png

https://cdn2.devleague.com/images/asset6.png

https://cdn2.devleague.com/images/asset7.png

https://cdn2.devleague.com/images/asset8.png

https://cdn2.devleague.com/images/asset9.png

https://cdn2.devleague.com/images/asset10.png

https://cdn3.devleague.com/images/asset11.png

https://cdn3.devleague.com/images/asset12.png

https://cdn3.devleague.com/images/asset13.png

https://cdn3.devleague.com/images/asset14.png

https://cdn3.devleague.com/images/asset15.png

https://cdn3.devleague.com/images/asset16.png

Loading...

Loading...

Loading...

Use server side rendering for first view

then js to load the rest

avoid client side rendering

https://facebook.github.io/react/docs/environments.html

const React = require('react');
const ReactDOMServer = require('react-dom/server');

...

app.get('/', (req, res) => {
  const element = React.createElement('div', null, 'Hello World!');
  res.send(ReactDOMServer.renderToString(element));
});
function ngApp(req, res) {
  let baseUrl = '/';
  let url = req.originalUrl || '/';

  let config: ExpressEngineConfig = {
    directives: [ App ],
    platformProviders: [
      {provide: ORIGIN_URL, useValue: 'http://localhost:3000'},
      {provide: BASE_URL, useValue: baseUrl},
    ],
    providers: [
      {provide: REQUEST_URL, useValue: url},
      provideRouter(routes),
      NODE_LOCATION_PROVIDERS,
      NODE_HTTP_PROVIDERS,
    ],
    async: true,
    preboot: false
  };

  res.render('index', config);
}
app.get('/', ngApp);
app.get('/home', ngApp);
app.get('/about', ngApp);

https://universal.angular.io

Better Perceived Performance

https://universal.angular.io

Optimized for Search Engines

Site Preview

facebook, twitter, etc.

Made with Slides.com