server and client rendering of single page apps
DESCRIPTION
How we built a micro-service for Wish List that renders a shared client/server-side single page app and what we've learned along the way. Source for View Assembler mentioned in the talk on GitHub: http://github.com/NET-A-PORTER/backbone-assemblerTRANSCRIPT
![Page 1: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/1.jpg)
WISH LIST server and client rendering of
single page apps
Thomas Heymann @thomasheymann
github.com/cyberthom
![Page 2: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/2.jpg)
WHAT WE’VE BUILT
![Page 3: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/3.jpg)
WISH LIST
• Shopping tool
• Track products
• Receive alerts
• Share lists
![Page 4: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/4.jpg)
GOALS & OBJECTIVES
• Improve UI
• Pull out wish list into separate stack of services
• Update front-end code using modern frameworks and best practices
– Perfect candidate for SPA due to mutability
• Minify duplication which led to inconsistencies
![Page 5: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/5.jpg)
SHARED CONCERNS
Server Client
USER INTERACTIONS LOGGING
MONITORING
ROUTING
PRESENTATION LOGIC
APPLICATION DATA
views / layout / templates
models / collections / business rules / API calls
events / animations
SITE FURNITURE
ANALYTICS
i18n
translations / date and currency formatting
STYLING
![Page 6: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/6.jpg)
• Developers get DRY and MV*
• Users get best of both worlds:
– fully rendered pages (instant content)
– responsiveness of SPA
• Business gets SEO
• Everybody’s happy 🌈😃🎉
ENVIRONMENT AGNOSTIC
![Page 7: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/7.jpg)
• Even ThoughtWorks agrees 👌
TECH RADAR
http://www.thoughtworks.com/radar/#/techniques
Client and server rendering with same code
Increasingly, HTML is rendered not only on the server but also on the client, in the web browser. In many cases this split rendering will remain a necessity but with the growing maturity of JavaScript templating libraries an interesting
approach has become viable: client and server rendering with same code.
HOLD ASSESS TRIAL ADOPT
![Page 8: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/8.jpg)
ARCHITECTURE & TECH STACK
![Page 9: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/9.jpg)
SYSTEMS OVERVIEW
WISHLIST API
SITE FURNITURE
Aggregation Layer Presentation Layer
API MAGAZINE
API CATALOGUE
Core Services & Storage
![Page 10: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/10.jpg)
TECH STACK
SERVER CLIENT
node.js IE8+
Express Backbone Router
cheerio jQuery
Backbone
Handlebars
require.js
App
![Page 11: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/11.jpg)
• Renders nested views • Re-attaches views to DOM client-side
APP COMPONENTS
VIEW ASSEMBLER
ROUTER
MODEL FACTORY
SERIALIZER
SYNCER • Ignores duplicate requests and caches responses • Injects headers
• Matches URLs and extracts params • Executes controller logic
• Serializes app data to JSON server-side • Deserializes app data client-side
• Creates models • Enforces singletons for data bindings
![Page 12: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/12.jpg)
PAGE REQUEST
1. Instantiate app
2. Dispatch route (run controller logic)
3. Render views
4. Serialize data (models/collections)
5. Decorate page with site furniture
6. Send response
SERVER WORKFLOW
![Page 13: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/13.jpg)
PAGE LOAD
1. Instantiate app
2. Deserialize data (models/collections)
3. Setup Backbone routes
4. Dispatch route (run controller logic)
5. Re-attach views
PAGE NAVIGATION
1. Dispatch route (run controller logic)
2. Render views
CLIENT WORKFLOW
![Page 14: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/14.jpg)
HANDS ON
![Page 15: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/15.jpg)
module.exports = function(options) { ! var options = options || {}, ! App = options.app, ! route = options.route; !! return function(req, res, next) { ! var app = new App({ ! language: req.params.language, ! authToken: req.cookies.sessionid! }); !! app! .dispatch(route, req.params) ! .done(function(view) { ! var html = view.render().outerHTML(), ! bootstrap = JSON.stringify(app.serialize()); ! res.send(html + '<script>' + bootstrap + '</script>'); ! }) ! .fail(next); ! }; !}; !
SERVE APP MIDDLEWARE
![Page 16: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/16.jpg)
WISH LIST CONTROLLER
route('/wishlist/:id', 'wishlist', function(id) { ! var app = this, api = app.wishlistApi; !! var allWishlists = api.getAllWishlists(), ! wishlist = api.getWishlist(id), ! allWishlistItems = api.getAllWishlistItems(id); !! var productList = new ProductListView({ ! collection: allWishlistItems! }), ! navigation = new NavigationView({ ! model: wishlist, ! collection: allWishlists! }); ! mainView = new MainView({ ! model: wishlist, ! views: { ! 'append header': navigation, ! 'inner [role=main]': productList! } ! }); !! return mainView.ready().then(function() { ! return mainView; ! }); !}); !
![Page 17: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/17.jpg)
MAIN TEMPLATE
<header> ! <h1>{{name}}</h1> !! {{#if isOwner}} ! <button>Manage</button> ! {{/if}} !! {{! Navigation }} !</header> !!<div role="main">{{! Product List }}</div> !
![Page 18: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/18.jpg)
CLIENT-SIDE BOOTSTRAPPING
<header> ! <h1>Wish List</h1> <button>Manage</button> ! <ul class="navigation"> ! <li>...</li> ! </ul> !</header> !<div role="main"> ! <ul class=“product-list"> ! <li>...</li> ! </ul> !</div> !!<script> ! var app = new App(); !! app.deserialize({bootstrap}); !! route('/wishlist/:id', 'wishlist', function(id) { ! app! .dispatch(route, id) ! .done(function(view) { ! view.attach('.container'); ! }); ! }); ! Backbone.history.start(); !</script> !
![Page 19: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/19.jpg)
VOILA!
![Page 20: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/20.jpg)
WHAT WE’VE LEARNED
![Page 21: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/21.jpg)
• Lots of code bridging differences between environments and libraries
– Introducing extra abstraction layer
• Routing is different
– Stateless server-side routing (new app instance for each request)
– Stateful client-side routing (holding on to app during page navigation)
– Express vs. Backbone routes (reversed order, paths, params)
– Redirects (302 vs. browser location)
– Serve mobile page based on user agent
• Fetching data is different (XMLHttpRequest vs. node request)
COMPLEX PROBLEM
![Page 22: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/22.jpg)
• Caching is different
– Only ever fetch once per incoming request
– Tab could be left open for days
– Expire and re-fetch resources after x seconds client-side
• Error handling is different (HTTP error codes vs. popups)
• Sending email is borderline
COMPLEX PROBLEM
![Page 23: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/23.jpg)
• Dependency management
– CommonJS runs client-side with browserify
– AMD runs server-side with require.js
– Both work but ideally language level support (ES6 Modules)
• Promises
– Q or jQuery Deferred
– Ideally one standard (ES6 Promises)
ES5 IMMATURITY
![Page 24: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/24.jpg)
• Well known jQuery API for DOM manipulation and traversal
• Easy to pick up
• Can translate Backbone apps directly to the server
• No full blown JSDOM, but still slow
CHEERIO
![Page 25: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/25.jpg)
• Airbnb Rendr
– One stop solution (Rendering, Serialization, Routing)
– Pure string concatenation using templates – Fast on the server
– Client-side rendering trashes and re-renders whole DOM (can cause flicker and performance issues)
• Facebook React
– View rendering library (Bring your own MC)
– Virtual DOM approach: slow on the server but super fast client-side
– Fun fact: Inspired by DOOM III
CHEERIO ALTERNATIVES
![Page 26: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/26.jpg)
Rendering comment box with 1000 comments
SERVER-SIDE PERFORMANCE
• Very simple HTML – String concatenation will shine even more as markup grows in complexity
• React has the edge over Cheerio – JSX uses precompiled objects so can be heavily optimized by V8 (Cheerio parses strings into trees on runtime)
RENDR
REACT
CHEERIO
~ 110 ms
~ 174 ms
~ 204 ms
![Page 27: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/27.jpg)
• Load performance means time to content (not response time)
– Server-side DOM is slow but faster than serving empty pages
• Think of it as an enhancement to SPA
– Stateless service can be easily scaled up
– Respond with empty pages if load is too high and render client-side
• Client-side rendering performance equally important
• Decide per use case – In certain situations simple Express.js templating with
separate client-side JS might be better approach
SO WHAT?
![Page 28: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/28.jpg)
• YES! 🌟😺✨
• Joy to develop once hard work is done
• Easy and fast to build rich UI and add new functionality
• But:
• Technique in its infants
• Better frameworks/solutions required
WORTH IT?
![Page 29: Server and client rendering of single page apps](https://reader030.vdocument.in/reader030/viewer/2022020306/554f6c61b4c905bb178b4ebb/html5/thumbnails/29.jpg)
THANKS!