Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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/

William Durand

May 10, 2018
Tweet

More Decks by William Durand

Other Decks in Programming

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. Before 2010 Before 2010 Server side frameworks Template engines jQuery,

    Yahoo UI script.aculo.us ❤ XMLHttpRequest 3 . 2
  4. 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
  5. 2013 2013 React initial release Interesting, but what is this

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

    Problem(s) solved Problem(s) solved 3 . 6
  7. 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
  8. Bene ts? Bene ts? Accessibility (limited) Better performances ( rst

    meaningful paint) Better user experience (JS disabled) Search Engine Optimization/Social sharing 4 . 4
  9. 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
  10. 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
  11. 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
  12. 1. T wo di erent environments, 1. T wo di

    erent environments, one codebase one codebase + isomorphic libraries and poly lls 4 . 8
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. Double render is a fragile Double render is a fragile

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

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

    status codes Correct error pages On both server and client 6 . 6
  23. 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
  24. 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
  25. No random allowed No random allowed There is a .

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

    isolated server context isolated server context 6 . 12
  27. 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
  28. 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
  29. 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
  30. 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
  31. You may not need SSR. You may not need SSR.

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

    Chrome: an answer to server-side rendering JS sites 7 . 3
  33. Thank You. Thank You. Questions? Questions? è ¾ ® ¬

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