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.

Spa, isomorphic and back to the server our journey with js @ frontend con poland 2017

304 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, as 3 years ago we decided to tackle our problems on mobile with an isomorphic application.

Today, we would like to guide you through 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.

Spoiler alert: 30ms is considered slow.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Spa, isomorphic and back to the server our journey with js @ frontend con poland 2017

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

×