Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Alex Nadalin - CTO @ namshi.com
JsDay 2017 (Verona - Italy)
WARNING
Controversy
ahead
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-iso
morphic-javascript-applications/
Clients are still angry
Sneak peak of a custom-made
universal js framework
Sneak peak of a custom-made
universal js framework
Sneak peak of a custom-made
universal js framework
Fonzie does not approve.
The do-over
Simplify our codebase
Better client-side performance
roi
Appealing looks
Best website?
It’s an app.
It’s an app.
Smooth transitions
It’s an app.
Smooth transitions
Great (perceived) performance
Can a website
Match 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 us
15/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',
}
_.pi...
Abstraction === cost
let obj = {
name: 'alex',
age: 28,
hair: 'enough',
status: 'married',
job: 'who really knows',
}
_.pi...
Abstraction === cost
let obj = {
name: 'alex',
age: 28,
hair: 'enough',
status: 'married',
job: 'who really knows',
}
_.pi...
Abstraction === cost
let obj = {
name: 'alex',
age: 28,
hair: 'enough',
status: 'married',
job: 'who really knows',
}
_.pi...
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...
Abstraction === cost
let obj = {
name: 'alex',
work: {
name: 'Namshi'
}
}
let work = null;
if (obj && obj.work && obj.work...
!
Results may vary
let attributes = [
‘price’,
‘name’,
‘description’,
‘url’
]
…
…
…
function sanitize(products) {
return products.map(p => {
...
let attributes = [
‘price’,
‘name’,
‘description’,
‘url’
]
…
…
…
function sanitize(products) {
return products.map(p => {
...
let attributes = [
‘price’,
‘name’,
‘description’,
‘url’
]
…
…
…
function sanitize(products) {
return products.map(p => {
...
let attributes = [
‘price’,
‘name’,
‘description’,
‘url’
]
…
…
…
function sanitize(products) {
return products.map(p => {
...
let attributes = [
‘price’,
‘name’,
‘description’,
‘url’
]
…
…
…
function sanitize(products) {
return products.map(p => {
...
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 / tot
2 conns
The old www
sprites
sprites
1mb / tot
1 conn
sprites
http/2
http/2
800kb / tot
1 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='...
preload
<link
rel="preload"
href="https://mycdn.com/fonts.css"
as="style"
onload="
this.rel='stylesheet';
this.className='...
preload
<link
rel="preload"
href="https://mycdn.com/fonts.css"
as="style"
onload="
this.rel='stylesheet';
this.className='...
preload
<link
rel="preload"
href="https://mycdn.com/fonts.css"
as="style"
onload="
this.rel='stylesheet';
this.className='...
preload
<link
rel="preload"
href="https://mycdn.com/fonts.css"
as="style"
onload="
this.rel='stylesheet';
this.className='...
preload
<link
rel="preload"
href="https://mycdn.com/fonts.css"
as="style"
onload="
this.rel='stylesheet';
this.className='...
preload
prerender
window.addEventListener('load', function(){
var preRenderLink = doc.createElement('link');
preRenderLink.rel='pr...
prerender
window.addEventListener('load', function(){
var preRenderLink = doc.createElement('link');
preRenderLink.rel='pr...
prerender
window.addEventListener('load', function(){
var preRenderLink = doc.createElement('link');
preRenderLink.rel='pr...
prerender
window.addEventListener('load', function(){
var preRenderLink = doc.createElement('link');
preRenderLink.rel='pr...
prerender
window.addEventListener('load', function(){
var preRenderLink = doc.createElement('link');
preRenderLink.rel='pr...
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
Less bandwidth, good cache rate
13 req
~380kb
RESULTS ?
-60%
AVG document content loaded time
1.93 vs 4.84
-14%
Bounce rate
+40%
AVG session duration
+30%
Conversion rate
Looking
Forward
to...
Not available in the browser
https://github.com/grpc/grpc/issues/8682
Under development
https://firebase.google.com/docs/cloud-messaging/
Under development
https://githubengineering.com/githubs-post-csp-journey/
Old, abandoned idea
https://youtu.be/v0xRTEf-ytE?t=16m25s
Still far, far away
https://github.com/jakearchibald/navigation-transitions
“Hot” is
overrated
NERD ADVICE
“Hot” is
overrated
https://jakearchibald.com/2016/ca
ching-best-practices/
“As you can see, you can hack around poor cachi...
“Solve problems
on the right
layer”
https://www.ampproject.org/lear
n/amp-design-principles/
NERD ADVICE
“Solve problems
on the right
layer”
https://www.ampproject.org/lear
n/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 <30ms
95% within <120ms
Take this any day
Measure $ wisely
http://250bpm.com/blog:86
NERD ADVICE
NERD ADVICE
Act upon what
moves the needle
https://en.wikipedia.org/wiki/Par
eto_principle
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
joind.in/talk/9e16d
Alessandro Nadalin
@_odino_
Namshi
CTO
odino.org
we are hiring!
tech.namshi.com/join-us
github.com/namshi
twitter.com/TechNamshi
tech.namshi.com
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)
Upcoming SlideShare
Loading in …5
×

SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)

594 views

Published on

We've been toying around with JS since 4 years, trying to figure out what's the best approach to build mobile-friendly apps that would offer the best performances on mobile devices.
We first went SPA and then decided to take a different approach.
2 years ago we presented a talk, at this very same conference, about how we decided to tackle our problems on mobile with an isomorphic application.

Today, we would like to guide you through 2 years of that choice and why we decided to take a step back and go revamp our mobile website again, with server-side rendering and a pinch of React.

Fast and wicked performance ahead!

Published in: Technology
  • Be the first to comment

  • Be the first to like this

SPA, isomorphic and back to the server: our journey with JavaScript @ JsDay 2017 in Verona (Italy)

  1. 1. Alex Nadalin - CTO @ namshi.com JsDay 2017 (Verona - Italy)
  2. 2. WARNING Controversy ahead
  3. 3. Take #1
  4. 4. Started with a SPA
  5. 5. ...which pissed bots off
  6. 6. ...which pissed clients off
  7. 7. ...which pissed clients off
  8. 8. ...which pissed clients off
  9. 9. Take #2
  10. 10. Isomorphic js
  11. 11. Both on the client & the server
  12. 12. universal js
  13. 13. Works only in movies
  14. 14. https://2015.jsday.it/talk/back-to-the-future-iso morphic-javascript-applications/
  15. 15. Clients are still angry
  16. 16. Sneak peak of a custom-made universal js framework
  17. 17. Sneak peak of a custom-made universal js framework
  18. 18. Sneak peak of a custom-made universal js framework
  19. 19. Fonzie does not approve.
  20. 20. The do-over
  21. 21. Simplify our codebase
  22. 22. Better client-side performance
  23. 23. roi
  24. 24. Appealing looks
  25. 25. Best website?
  26. 26. It’s an app.
  27. 27. It’s an app. Smooth transitions
  28. 28. It’s an app. Smooth transitions Great (perceived) performance
  29. 29. Can a website Match that?
  30. 30. Spa closely bridges the gap
  31. 31. Spa closely bridges the gap Additional layer of complexity
  32. 32. Generally no. Spa closely bridges the gap Additional layer of complexity
  33. 33. Do we need it?
  34. 34. service is king
  35. 35. The b* stack
  36. 36. The b* stack
  37. 37. The b* stack
  38. 38. The b* stack
  39. 39. The b* stack
  40. 40. server
  41. 41. client
  42. 42. Webpack 2
  43. 43. Webpack 2 Tree-shaking saved us 15/20% of the gzipped bundle size
  44. 44. Say “no” to jquery(as much as possible)
  45. 45. Abstraction === cost
  46. 46. 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, }
  47. 47. 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, }
  48. 48. 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, }
  49. 49. 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
  50. 50. Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } } _.get(obj, 'work.name', null)
  51. 51. Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } } _.get(obj, 'work.name', null) 1.3m ops/s
  52. 52. Abstraction === cost let obj = { name: 'alex', work: { name: 'Namshi' } } let work = null; if (obj && obj.work && obj.work.name) { work = obj.work.name }
  53. 53. 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
  54. 54. ! Results may vary
  55. 55. let attributes = [ ‘price’, ‘name’, ‘description’, ‘url’ ] … … … function sanitize(products) { return products.map(p => { return _.pick(p, attributes) }) }
  56. 56. let attributes = [ ‘price’, ‘name’, ‘description’, ‘url’ ] … … … function sanitize(products) { return products.map(p => { return _.pick(p, attributes) }) }
  57. 57. let attributes = [ ‘price’, ‘name’, ‘description’, ‘url’ ] … … … function sanitize(products) { return products.map(p => { return _.pick(p, attributes) }) }
  58. 58. let attributes = [ ‘price’, ‘name’, ‘description’, ‘url’ ] … … … function sanitize(products) { return products.map(p => { return _.pick(p, attributes) }) } 5ms / req
  59. 59. let attributes = [ ‘price’, ‘name’, ‘description’, ‘url’ ] … … … function sanitize(products) { return products.map(p => { return _.pick(p, attributes) }) } 10% / req
  60. 60. const _ = require(‘lodash’)
  61. 61. const _ = require(‘lodash’) const pick = require(‘lodash/pick’)
  62. 62. articles .filter(a => a.active) .map(a => { a.title = titleCase(a.title) return a })
  63. 63. articles .filter(a => a.active) .map(a => { a.title = titleCase(a.title) return a })
  64. 64. articles .filter(a => a.active) .map(a => { a.title = titleCase(a.title) return a })
  65. 65. articles .filter(a => a.active) .map(a => { a.title = titleCase(a.title) return a })
  66. 66. articles .filter(a => a.active) .map(a => { a.title = titleCase(a.title) return a }) 700k ops/s
  67. 67. articles .reduce((acc, a) => { if (a.active) { a.title = titlecase(a.title) acc.push(a) } return acc })
  68. 68. articles .reduce((acc, a) => { if (a.active) { a.title = titlecase(a.title) acc.push(a) } return acc }) 4M ops/s
  69. 69. sprites?
  70. 70. No thanks, I do http/2
  71. 71. 300kb 500kb 200kb
  72. 72. The old www
  73. 73. The old www
  74. 74. 300kb 500kb The old www
  75. 75. 300kb 500kb 800kb / tot 2 conns The old www
  76. 76. sprites
  77. 77. sprites
  78. 78. 1mb / tot 1 conn sprites
  79. 79. http/2
  80. 80. http/2
  81. 81. 800kb / tot 1 conn http/2
  82. 82. react?
  83. 83. “No sauce please”
  84. 84. “No sauce please” react-lite 25kb preact 3kb
  85. 85. css animations
  86. 86. css animations
  87. 87. Page transition
  88. 88. preconnect <html class="en"> <head> <link preconnect="https://a.namshicdn.com" crossorigin />
  89. 89. preconnect <html class="en"> <head> <link preconnect="https://a.namshicdn.com" crossorigin />
  90. 90. preconnect <html class="en"> <head> <link preconnect="https://a.namshicdn.com" crossorigin /> Initiate connections ASAP
  91. 91. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " />
  92. 92. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " />
  93. 93. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " />
  94. 94. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " />
  95. 95. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " />
  96. 96. preload <link rel="preload" href="https://mycdn.com/fonts.css" as="style" onload=" this.rel='stylesheet'; this.className='font-loaded' " /> Async Non-blocking CSS
  97. 97. preload
  98. 98. prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
  99. 99. prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
  100. 100. prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
  101. 101. prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
  102. 102. prerender window.addEventListener('load', function(){ var preRenderLink = doc.createElement('link'); preRenderLink.rel='prerender'; preRenderLink.href= '{{ nextPage }}'; document.head.appendChild(preRenderLink); });
  103. 103. prerender
  104. 104. SPLIT ASSETS
  105. 105. SPLIT ASSETS
  106. 106. SPLIT ASSETS
  107. 107. index.html
  108. 108. detail.html
  109. 109. Less bandwidth, good cache rate
  110. 110. Less bandwidth, good cache rate 7 req ~100kb
  111. 111. Less bandwidth, good cache rate 9 req ~150kb
  112. 112. Less bandwidth, good cache rate 13 req ~380kb
  113. 113. RESULTS ?
  114. 114. -60% AVG document content loaded time 1.93 vs 4.84
  115. 115. -14% Bounce rate
  116. 116. +40% AVG session duration
  117. 117. +30% Conversion rate
  118. 118. Looking Forward to...
  119. 119. Not available in the browser https://github.com/grpc/grpc/issues/8682
  120. 120. Under development https://firebase.google.com/docs/cloud-messaging/
  121. 121. Under development https://githubengineering.com/githubs-post-csp-journey/
  122. 122. Old, abandoned idea https://youtu.be/v0xRTEf-ytE?t=16m25s
  123. 123. Still far, far away https://github.com/jakearchibald/navigation-transitions
  124. 124. “Hot” is overrated NERD ADVICE
  125. 125. “Hot” is overrated https://jakearchibald.com/2016/ca ching-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
  126. 126. “Solve problems on the right layer” https://www.ampproject.org/lear n/amp-design-principles/ NERD ADVICE
  127. 127. “Solve problems on the right layer” https://www.ampproject.org/lear n/amp-design-principles/ NERD ADVICE
  128. 128. Servers can still be pretty darn fast. NERD ADVICE
  129. 129. NERD ADVICE
  130. 130. Servers can still be pretty fast NERD ADVICE
  131. 131. Servers can still be pretty fast NERD ADVICE
  132. 132. Servers can still be pretty fast NERD ADVICE
  133. 133. 50% within <30ms
  134. 134. 95% within <120ms
  135. 135. Take this any day
  136. 136. Measure $ wisely http://250bpm.com/blog:86 NERD ADVICE
  137. 137. NERD ADVICE Act upon what moves the needle https://en.wikipedia.org/wiki/Par eto_principle
  138. 138. Alessandro Nadalin
  139. 139. Alessandro Nadalin @_odino_
  140. 140. Alessandro Nadalin @_odino_ Namshi
  141. 141. Alessandro Nadalin @_odino_ Namshi CTO
  142. 142. Alessandro Nadalin @_odino_ Namshi CTO odino.org
  143. 143. Thanks! Alessandro Nadalin @_odino_ Namshi CTO odino.org
  144. 144. joind.in/talk/9e16d Alessandro Nadalin @_odino_ Namshi CTO odino.org
  145. 145. we are hiring! tech.namshi.com/join-us github.com/namshi twitter.com/TechNamshi tech.namshi.com

×