Extreme Web Performance Optimization
Who Are We?
- we build web apps
- we build web developers
- 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.
Backup of Extreme Web Performance Optimization
By Jon Borgonia
Backup of Extreme Web Performance Optimization
cause slides.com crashes and deletes my data
- 2,005