Server Side Rendering from the trenches

Server Side Rendering from the trenches

Server Side Rendering (SSR) is a technique to reuse client-side JavaScript code on the server. The main advantage is to speed up the first rendering time and improve the user experience on page load. It is considered one of the most complicated features to implement in the JS ecosystem. Yet, web apps with a high traffic often need it. In this talk, I’ll start by introducing the concept of SSR, followed by examples with React, Vue and some other frameworks (like Next or Nuxt). The last part will be dedicated to what I've learned building the new Mozilla Add-ons frontend.

Online slides: http://slides.williamdurand.fr/ssr-from-the-trenches/

F59d2f1ed66b8d9c6ceebea5a748494b?s=128

William Durand

May 10, 2018
Tweet

Transcript

  1. Server Side Rendering Server Side Rendering from the trenches from

    the trenches William Durand, jsDay 2018 1
  2. ⚠ ⚠ I am going to talk about server side

    rendering applied to JavaScript applications and that are usually rendered in web browsers, a.k.a. universal/isomorphic apps. 2
  3. A bit of history A bit of history 3 .

    1
  4. Before 2010 Before 2010 Server side frameworks Template engines jQuery,

    Yahoo UI script.aculo.us ❤ XMLHttpRequest 3 . 2
  5. 2011 2011 Backbone.js is 3 months old Node.js is 2

    years old People read Fielding's dissertation (REST) Let's write client side applications in JavaScript! Let's write client side applications in JavaScript! 3 . 3
  6. Client Side Rendering Client Side Rendering 3 . 4

  7. 2013 2013 React initial release Interesting, but what is this

    Flux architecture again? Interesting, but what is this Flux architecture again? 3 . 5
  8. 2015 2015 Vue.js is 1 year old Redux initial release

    Problem(s) solved Problem(s) solved 3 . 6
  9. Since then... Since then... “Can we render this JavaScript app

    on the server?” 3 . 7
  10. Server Side Rendering Server Side Rendering 4 . 1

  11. The big picture The big picture

  12. How it works How it works On every incoming request,

    the server: 1. creates the store/initial app state 2. matches the URL to nd the right component 3. loads the component and gets the HTML 4. sends the HTML back to the client Then, the client loads the JavaScript app. 4 . 3
  13. Bene ts? Bene ts? Accessibility (limited) Better performances ( rst

    meaningful paint) Better user experience (JS disabled) Search Engine Optimization/Social sharing 4 . 4
  14. Googlebot Googlebot Quite good at browsing JS apps Give up

    after ~10 seconds Some issues with react-router Source: , Nov. 2016. Testing a React-driven website’s SEO using “Fetch as Google” 4 . 5
  15. Drawbacks? Drawbacks? Makes everything very complicated Time To First Byte

    (TTFB) usually slower (but Cloudfare says it's ne in [1]) React renderToString() holds the event loop [2], probably also the case for other frameworks [1]: [2]: Stop worrying about Time To First Byte (TTFB) The Bene ts of Server Side Rendering Over Client Side Rendering 4 . 6
  16. Why is it so complicated? Why is it so complicated?

    1. Two di erent environments, one codebase 2. Cookies, redirects/errors, HTTP statuses 3. Data fetching before rendering 4 . 7
  17. 1. T wo di erent environments, 1. T wo di

    erent environments, one codebase one codebase + isomorphic libraries and poly lls 4 . 8
  18. 2. Cookies, redirects/errors, 2. Cookies, redirects/errors, HTTP statuses HTTP statuses

    You have to nd nice tricks hacks 4 . 9
  19. 3. Data fetching 3. Data fetching before before rendering rendering

    There used to be two approaches... static async method to fetch data and Promise.all() on the server double render 4 . 10
  20. 4 . 11

  21. Some examples Some examples 5 . 1

  22. React React ReactDOMServer.renderToString() That's all folks! 5 . 2

  23. Naive example (1/4) Naive example (1/4) Load the application //

    server.js (express app) app.use((req, res) => { const context = {}; // 1. Create the store. const store = configureStore(); // 2. Render the application using a `StaticRouter`. const markup = renderToString( <Provider store={store}> <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> </Provider> ); // see next slide... 5 . 3
  24. Naive example (2/4) Naive example (2/4) Send the full HTML

    to the client if (context.url) { redirect(301, context.url); // A `<Redirect>` was rendered. } else { // 3. Replace placeholders by generated state and HTML. const preloadedState = store.getState(); const html = INDEX_HTML .replace('__SSR__', markup) .replace('__PRELOADED_STATE__ = {}', [ `__PRELOADED_STATE__ =`, JSON.stringify(preloadedState).replace(/</g, '\\u003c'), ].join(' ')); } res.send(html); // 4. Send the HTML to the client. }); }); 5 . 4
  25. Naive example (3/4) Naive example (3/4) Add placeholders in the

    index.html <!-- index.html [...] --> <noscript> You need to enable JavaScript to run this app. </noscript> - <div id="root"></div> + <div id="root">__SSR__</div> + <script> + window.__PRELOADED_STATE__ = {}; + </script> </body> 5 . 5
  26. Naive example (4/4) Naive example (4/4) Use the state generated

    on the server, if any // src/index.js -const store = configureStore(); +const preloadedState = window.__PRELOADED_STATE__ || {}; +// Allow the passed state to be garbage-collected +delete window.__PRELOADED_STATE__; + +const store = configureStore(preloadedState); 5 . 6
  27. But... But... No data fetching No error handling 5 .

    7
  28. Next.js Next.js Powerful React-based , SSR-ready. framework class UsersList extends

    React.Component { static async getInitialProps({ store, isServer, ...props }) { const users = await getUsers(); return { users }; } render() { const { users } = this.props; // ... } } 5 . 8
  29. Vue.js Vue.js SSR is o cially supported is pure gold

    ssr.vuejs.org <!-- UsersList.vue --> <template></template> <script> export default { asyncData ({ store }) { return store.dispatch('getUsers'); } } </script> 5 . 9
  30. Nuxt.js Nuxt.js Vue-based framework, inspired by Next.js Implement what is

    described in ssr.vuejs.org 5 . 10
  31. Some lessons learnt Some lessons learnt 6 . 1

  32. 6 . 2

  33. addons.mozilla.org addons.mozilla.org Universal React/Redux app i18n/l10n, CSP Open Source: mozilla/addons-frontend

    6 . 3
  34. Double render is a fragile Double render is a fragile

    hack, do not use it hack, do not use it 6 . 4
  35. Always be careful Always be careful Unde ned reference on

    the server == Error 500. #GameOver 6 . 5
  36. Error handling is tough Error handling is tough Accurate HTTP

    status codes Correct error pages On both server and client 6 . 6
  37. Debugging made Debugging made complex complex Isomorphic logging layer but

    no dev tools disableSSR con g option to the rescue! but some issues are hidden easy easy 6 . 7
  38. Server logs Server logs [...] INFO: proxy: 302 ~> http://127.0.0.1:3333/service-worker.js

    (app= INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/account INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/account INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/account WARN: server: restrictSearchResultsToAppVersion config set; not s WARN: server: restrictSearchResultsToAppVersion config set; not s WARN: server: restrictSearchResultsToAppVersion config set; not s INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/addons/ INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/addons/ INFO: proxy: 200 ~> https://addons-dev.allizom.org/api/v3/addons/ INFO: server: Second component render after sagas have finished ( INFO: proxy: 200 ~> http://127.0.0.1:3333/en-US/firefox/ (app=amo WARN: server: CSP has been disabled from the config (app=amo) INFO: server: Prepending lang to URL: en-US (app=amo) INFO: server: Prepending application to URL: firefox (app=amo) 6 . 8
  39. New fun bugs New fun bugs 6 . 9

  40. Example Example 6 . 10

  41. No random allowed No random allowed There is a .

    React RFC for introducing isomorphic IDs 6 . 11
  42. You must have a fresh, You must have a fresh,

    isolated server context isolated server context 6 . 12
  43. Example Example $ repeat 10 curl https://addons.mozilla.org/en-US/firefox/addon/a | grep --color

    -oE 'updated</dt><dd data-reactid=\"\d+\">.+?</d updated</dt><dd data-reactid="194">il y a 2 jours (6 nov. 2017)</ updated</dt><dd data-reactid="194">2 days ago (Nov 6, 2017)</dd> updated</dt><dd data-reactid="194">2 days ago (Nov 6, 2017)</dd> updated</dt><dd data-reactid="194">2 hari yang lalu (6 Nov 2017)< updated</dt><dd data-reactid="194">2 days ago (Nov 6, 2017)</dd> updated</dt><dd data-reactid="194">2 days ago (6 Nov 2017)</dd> updated</dt><dd data-reactid="194">2 dias atrás (6 de nov de 2017 updated</dt><dd data-reactid="194">2 days ago (Nov 6, 2017)</dd> updated</dt><dd data-reactid="194">2 (2017 11 6 )</dd> updated</dt><dd data-reactid="194">2 days ago (Nov 6, 2017)</dd> 6 . 13
  44. You need more servers You need more servers In order

    to reduce the # of 500 caused by the issue, we had to bump up the # of instances in the cluster from 8 to 40. And that eventually stopped the "heap oom" from continuously happening. 6 . 14
  45. Security considerations Security considerations State serialization when transferring the Redux

    state from the server to the client [1] Sensitive data on the server, e.g., env vars [1]: Redux Server Rendering 6 . 15
  46. React has useful React has useful dev warnings dev warnings

    TL;DR: React on the client does not generate the same HTML sent by the server: there is a bug. [1] [1]: What’s New With Server-Side Rendering in React 16 6 . 16
  47. We can test (pretty much) We can test (pretty much)

    everything everything 6 . 17
  48. So what? So what? 7 . 1

  49. You may not need SSR. You may not need SSR.

    If you need it, use a framework that is SSR-ready. 7 . 2
  50. Other ideas Other ideas Progressive Web Apps/Service workers? Prerender.io Headless

    Chrome: an answer to server-side rendering JS sites 7 . 3
  51. Thank you to my awesome team! 7 . 4

  52. Thank You. Thank You. Questions? Questions? è ¾ ® ¬

    joind.in/talk/b263d williamdurand.fr github.com/willdurand twitter.com/couac 8