spa, isomorphic and back to the server our journey with js @ frontend con poland 2017
TRANSCRIPT
Alex Nadalin - CTO @ namshi.comFRONTEND CON 2017 (WARSAW - poland)
WARNING
Controversyahead
WARNING
Insult@_odino_
Zalando of the middle east
http://tech.namshi.io/blog/2017/05/02/rebuilding-our-mobile-website/
Shidhin CR
https://developers.google.com/experts/people/shidhin-cr
Mohamed amin
https://medium.com/@MohamedAmin88
Gabriel izebhigie
http://tech.namshi.io/team/#Gabriel Izebhigie
Take #1
Started with a SPA
...which pissed bots off
...which pissed clients off
...which pissed clients off
...which pissed clients off
Take #2
Isomorphic js
Both on the client & the server
universal js
Works only in movies
https://2015.jsday.it/talk/back-to-the-future-isomorphic-javascript-applications/
Clients are still angry
Sneak peak of a custom-madeuniversal js framework
Sneak peak of a custom-madeuniversal js framework
Sneak peak of a custom-madeuniversal js framework
Fonzie does not approve.
The do-over
Simplify our codebase
Better client-side performance
roi
Best website?
It’s an app.
It’s an app.
Smooth transitions
It’s an app.
Smooth transitions
Great (perceived) performance
Can a websiteMatch that?
Spa closely bridges the gap
Spa closely bridges the gap
Additional layer of complexity
Generally no.
Spa closely bridges the gap
Additional layer of complexity
Do we need it?
service is king
The b* stack
The b* stack
The b* stack
The b* stack
The b* stack
server
client
Webpack 2
Webpack 2
Tree-shaking saved us15/20% of the gzipped
bundle size
Say “no” to jquery(as much as possible)
Abstraction === cost
Abstraction === cost let obj = { name: 'alex', age: 28, hair: 'enough', status: 'married', job: 'who really knows', }
_.pick(obj, ['name', 'age'])
{ name: obj.name, age: obj.age,}
Abstraction === cost let obj = { name: 'alex', age: 28, hair: 'enough', status: 'married', job: 'who really knows', }
_.pick(obj, ['name', 'age'])
{ name: obj.name, age: obj.age,}
Abstraction === cost let obj = { name: 'alex', age: 28, hair: 'enough', status: 'married', job: 'who really knows', }
_.pick(obj, ['name', 'age'])
{ name: obj.name, age: obj.age,}
Abstraction === cost let obj = { name: 'alex', age: 28, hair: 'enough', status: 'married', job: 'who really knows', }
_.pick(obj, ['name', 'age'])
{ name: obj.name, age: obj.age,}700k ops/s 75m ops/s
Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } }
_.get(obj, 'work.name', null)
Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } }
_.get(obj, 'work.name', null)
1.3m ops/s
Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } }
let work = null;
if (obj && obj.work && obj.work.name) { work = obj.work.name }
Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } }
let work = null;
if (obj && obj.work && obj.work.name) { work = obj.work.name }
70m ops/s
!Results may vary
let attributes = [‘price’,‘name’,‘description’,‘url’
]
………
function sanitize(products) {return products.map(p => {
return _.pick(p, attributes) })}
let attributes = [‘price’,‘name’,‘description’,‘url’
]
………
function sanitize(products) {return products.map(p => {
return _.pick(p, attributes) })}
let attributes = [‘price’,‘name’,‘description’,‘url’
]
………
function sanitize(products) {return products.map(p => {
return _.pick(p, attributes) })}
let attributes = [‘price’,‘name’,‘description’,‘url’
]
………
function sanitize(products) {return products.map(p => {
return _.pick(p, attributes) })} 5ms / req
let attributes = [‘price’,‘name’,‘description’,‘url’
]
………
function sanitize(products) {return products.map(p => {
return _.pick(p, attributes) })} 10% / req
const _ = require(‘lodash’)
const _ = require(‘lodash’)
const pick = require(‘lodash/pick’)
articles.filter(a => a.active).map(a => {a.title = titleCase(a.title)
return a})
articles.filter(a => a.active).map(a => {a.title = titleCase(a.title)
return a})
articles.filter(a => a.active).map(a => {a.title = titleCase(a.title)
return a})
articles.filter(a => a.active).map(a => {a.title = titleCase(a.title)
return a})
articles.filter(a => a.active).map(a => {a.title = titleCase(a.title)
return a})
700k ops/s
articles.reduce((acc, a) => {if (a.active) {a.title = titlecase(a.title)acc.push(a)
}
return acc})
articles.reduce((acc, a) => {if (a.active) {a.title = titlecase(a.title)acc.push(a)
}
return acc}) 4M ops/s
sprites?
No thanks,I do http/2
300kb 500kb 200kb
The old www
The old www
300kb 500kb
The old www
300kb 500kb 800kb / tot2 conns
The old www
sprites
sprites
1mb / tot1 conn
sprites
http/2
http/2
800kb / tot1 conn
http/2
react?
“No sauce please”
“No sauce please”
react-lite 25kb
preact 3kb
css animations
css animations
Page transition
preconnect
<html class="en"><head>
<link preconnect="https://a.namshicdn.com" crossorigin />
preconnect
<html class="en"><head>
<link preconnect="https://a.namshicdn.com" crossorigin />
preconnect
<html class="en"><head>
<link preconnect="https://a.namshicdn.com" crossorigin />Initiate
connections
ASAP
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
preload<link
rel="preload" href="https://mycdn.com/fonts.css" as="style" onload="
this.rel='stylesheet';this.className='font-loaded'
"/>
Async
Non-blocking
CSS
preload
prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
prerender
SPLIT ASSETS
SPLIT ASSETS
SPLIT ASSETS
index.html
detail.html
Less bandwidth, good cache rate
Less bandwidth, good cache rate
7 req~100kb
Less bandwidth, good cache rate
9 req~150kb
RESULTS ?
-60%AVG document content loaded time
1.93 vs 4.84
-14%Bounce rate
+40%AVG session duration
+30%Conversion rate
LookingForward
to...
Not available in the browserhttps://github.com/grpc/grpc/issues/8682
Under developmenthttps://firebase.google.com/docs/cloud-messaging/
Under developmenthttps://githubengineering.com/githubs-post-csp-journey/
NERD ADVICE
NERD ADVICEAct upon what moves the needlehttps://en.wikipedia.org/wiki/Pareto_principle
“Hot” is overrated
NERD ADVICE
“Hot” is overratedhttps://jakearchibald.com/2016/caching-best-practices/
“As you can see, you can hack around poor caching in your service worker, but you're way better off fixing the root of the problem. Getting your caching right makes things easier in service worker land, but also benefits browsers that don't support service worker (Safari, IE/Edge), and lets you get the most out of your CDN.”
https://jakearchibald.com/2016/caching-best-practices/
NERD ADVICE
“Solve problems on the right layer”https://www.ampproject.org/learn/amp-design-principles/
NERD ADVICE
“Solve problems on the right layer”https://www.ampproject.org/learn/amp-design-principles/
NERD ADVICE
Servers can still be pretty darn fast.
NERD ADVICE
NERD ADVICE
Servers can still be pretty fast
NERD ADVICE
Servers can still be pretty fast
NERD ADVICE
Servers can still be pretty fast
NERD ADVICE
50% within <20ms
95% within <120ms
Take this any day
Alessandro Nadalin
Alessandro Nadalin
@_odino_
Alessandro Nadalin
@_odino_
Namshi
Alessandro Nadalin
@_odino_
Namshi
CTO
Alessandro Nadalin
@_odino_
Namshi
CTO
odino.org
Thanks!Alessandro Nadalin
@_odino_
Namshi
CTO
odino.org
we are hiring!tech.namshi.com/join-us
github.com/namshi
twitter.com/TechNamshi
tech.namshi.io