Slide 1

Slide 1 text

Building Universal Web Apps with React Elyse Kolker Gordon Twitter: @sfdrummerjs, Github: @elyseko Web Lead, Vevo Code: bit.ly/universal-app-react

Slide 2

Slide 2 text

Get it 40% off! bit.ly/isomorphicdevjs Code: ctwforwjs17 Win a free ebook: http://bit.ly/2m0pCiP

Slide 3

Slide 3 text

Universal or Isomorphic?

Slide 4

Slide 4 text

Write code once, run it in two environments (node & browser).

Slide 5

Slide 5 text

Overview of Universal/Isomorphic Web Apps Why would you want to build an universal web app Universal App Architecture Challenges and Tradeoffs Topics:

Slide 6

Slide 6 text

Overview: What is a Universal App?

Slide 7

Slide 7 text

Server-side rendered applications Overview

Slide 8

Slide 8 text

Server-side rendered applications Overview

Slide 9

Slide 9 text

Server-side rendered applications Overview

Slide 10

Slide 10 text

Server-side rendered applications Overview

Slide 11

Slide 11 text

Server-side rendered applications Overview

Slide 12

Slide 12 text

Server-side rendered applications Overview

Slide 13

Slide 13 text

Single Page Application (SPA) Overview

Slide 14

Slide 14 text

Single Page Application (SPA) Overview

Slide 15

Slide 15 text

Single Page Application (SPA) Overview

Slide 16

Slide 16 text

Single Page Application (SPA) Overview

Slide 17

Slide 17 text

Single Page Application (SPA) Overview

Slide 18

Slide 18 text

Why not both? Overview

Slide 19

Slide 19 text

Overview

Slide 20

Slide 20 text

Overview

Slide 21

Slide 21 text

Overview

Slide 22

Slide 22 text

Overview

Slide 23

Slide 23 text

Overview

Slide 24

Slide 24 text

Overview

Slide 25

Slide 25 text

Overview

Slide 26

Slide 26 text

Overview

Slide 27

Slide 27 text

Why build it?

Slide 28

Slide 28 text

#1 SEO Getting your website (or cat video) to rank the highest can make or break your success as a business. Why build it?

Slide 29

Slide 29 text

#2 Perceived Performance Users should think the site is fast. This means showing content (not loading spinners) as soon as possible. Why build it?

Slide 30

Slide 30 text

Everything is JavaScript! Most of your code will run on both the server and the browser. #3 Maintenance and Developer benefits Why build it?

Slide 31

Slide 31 text

Universal App Architecture

Slide 32

Slide 32 text

In this section Technologies required to build an universal app Loading a single page of the app in the isomorphic flow Architecture Code: bit.ly/universal-app-react

Slide 33

Slide 33 text

In this section Technologies required to build an universal app Loading a single page of the app in the isomorphic flow Architecture Code: bit.ly/universal-app-react

Slide 34

Slide 34 text

In this section Technologies required to build an universal app Loading a single page of the app in the isomorphic flow Architecture Code: bit.ly/universal-app-react

Slide 35

Slide 35 text

Architecture

Slide 36

Slide 36 text

Architecture

Slide 37

Slide 37 text

Node + Express Architecture

Slide 38

Slide 38 text

Webpack Architecture

Slide 39

Slide 39 text

Why webpack? NPM packages ES6/ES7 with babel CSS in your browser bundle Advanced features Architecture: webpack

Slide 40

Slide 40 text

Architecture: webpack

Slide 41

Slide 41 text

Architecture: webpack Code

Slide 42

Slide 42 text

Architecture: webpack Code Parser

Slide 43

Slide 43 text

Architecture: webpack Code Parser Transforms

Slide 44

Slide 44 text

Architecture: webpack Code Parser Transforms Compiled App Code

Slide 45

Slide 45 text

Architecture: wepack "scripts": {
 "build:browser": "webpack",
 "prestart": "npm run build:browser",
 "start": "node src/server.js"
 } package.json

Slide 46

Slide 46 text

module.exports = {
 entry: "./src/main.jsx",
 devtool: "source-map",
 output: {
 path: __dirname + '/ src/',
 filename: "browser.js"
 }, Architecture: webpack webpack.config.js

Slide 47

Slide 47 text

module.exports = {
 ... Architecture: webpack module: {
 loaders: [
 {
 test: /\.(jsx|es6)$/,
 exclude: /node_modules|examples/,
 loader: "babel-loader"
 },
 {
 test: /\.css$/,
 loaders: ['style', 'css']
 }
 ] webpack.config.js

Slide 48

Slide 48 text

React Architecture

Slide 49

Slide 49 text

Virtual DOM Architecture: React

Slide 50

Slide 50 text

Virtual DOM Architecture: React

Slide 51

Slide 51 text

Virtual DOM Architecture: React

Slide 52

Slide 52 text

Virtual DOM Architecture: React

Slide 53

Slide 53 text

Architecture: React import ReactDOM from 'react-dom';
 ReactDOM.render( , document.getElementById(‘react- content’) );

Slide 54

Slide 54 text

Architecture: React import { renderToString } from 'react-dom/ server';
 renderToString();

Slide 55

Slide 55 text

React Router Architecture

Slide 56

Slide 56 text

Architecture: React Router

Slide 57

Slide 57 text

Architecture: React Router ReactDOM.render(
 ,
 document.getElementById('react-content')
 );

Slide 58

Slide 58 text

Architecture: React Router renderView.jsx

Slide 59

Slide 59 text

import React from 'react';
 import { match, RouterContext } from 'react-router';
 import { routes } from '../shared/sharedRoutes';
 
 export default function renderView(req, res, next) {
 }
 Architecture: React Router renderView.jsx

Slide 60

Slide 60 text

import React from 'react';
 import { match, RouterContext } from 'react-router';
 import { routes } from '../shared/sharedRoutes';
 
 export default function renderView(req, res, next) {
 }
 Architecture: React Router match(matchOpts, handleMatchResult); renderView.jsx

Slide 61

Slide 61 text

import React from 'react';
 import { match, RouterContext } from 'react-router';
 import { routes } from '../shared/sharedRoutes';
 
 export default function renderView(req, res, next) {
 const matchOpts = {
 routes: routes(),
 location: req.url
 };
 
 
 }
 Architecture: React Router match(matchOpts, handleMatchResult); renderView.jsx match(matchOpts, handleMatchResult);

Slide 62

Slide 62 text

import React from 'react';
 import { match, RouterContext } from 'react-router';
 import { routes } from '../shared/sharedRoutes';
 
 export default function renderView(req, res, next) {
 const matchOpts = {
 routes: routes(),
 location: req.url
 };
 
 
 }
 Architecture: React Router const handleMatchResult = (error, redirectLocation, renderProps) => {
 if (!error && !redirectLocation && renderProps) { // render code } } match(matchOpts, handleMatchResult); renderView.jsx match(matchOpts, handleMatchResult); const matchOpts = {
 routes: routes(),
 location: req.url
 };


Slide 63

Slide 63 text

Redux Architecture

Slide 64

Slide 64 text

Architecture: Redux

Slide 65

Slide 65 text

View Architecture: Redux

Slide 66

Slide 66 text

View Actions Architecture: Redux

Slide 67

Slide 67 text

View Actions Architecture: Redux Reducers

Slide 68

Slide 68 text

View Actions Store Architecture: Redux Reducers

Slide 69

Slide 69 text

View Actions Store Architecture: Redux Reducers

Slide 70

Slide 70 text

Build the cart Architecture

Slide 71

Slide 71 text

Architecture: Create the Cart

Slide 72

Slide 72 text

Architecture: Create the Cart sharedRoutes.jsx

Slide 73

Slide 73 text


 
 
 
 
 
 Architecture: Create the Cart sharedRoutes.jsx

Slide 74

Slide 74 text

Architecture: Create the Cart export const routes = () => { } 
 
 
 
 
 
 sharedRoutes.jsx

Slide 75

Slide 75 text

Architecture: Create the Cart

Slide 76

Slide 76 text

Architecture: Create the Cart

Slide 77

Slide 77 text

Architecture: Create the Cart

Slide 78

Slide 78 text

Architecture: Create the Cart item.jsx

Slide 79

Slide 79 text

const Item = (props) => {
 };
 Architecture: Create the Cart item.jsx

Slide 80

Slide 80 text

return (

cart

 {props.name}


 ${props.price}


 );
 const Item = (props) => {
 };
 Architecture: Create the Cart item.jsx

Slide 81

Slide 81 text

return (

cart

 {props.name}


 ${props.price}


 );
 const Item = (props) => {
 };
 Architecture: Create the Cart item.jsx

Slide 82

Slide 82 text

return (

cart

 {props.name}


 ${props.price}


 );
 return (

cart

 {props.name}


 ${props.price}


 );
 const Item = (props) => {
 };
 const Item = (props) => {
 };
 Architecture: Create the Cart item.jsx

Slide 83

Slide 83 text

Architecture: Create the Cart cart.jsx

Slide 84

Slide 84 text

render() {
 return (


 {this.renderItems()}


 );
 } Architecture: Create the Cart cart.jsx

Slide 85

Slide 85 text

render() {
 return (


 {this.renderItems()}


 );
 } Architecture: Create the Cart
Total: ${this.getTotal()}
 
 Checkout
 

render() {
 return (


 {this.renderItems()}


 );
 } cart.jsx

Slide 86

Slide 86 text


 import cartActions from '../shared/cart-action-creators.es6';
 
 export class CartComponent extends Component {
 
 static loadData() {
 return [
 cartActions.getCartItems
 ];
 }
 render() {}
 }
 Architecture: Create the Cart cart.jsx

Slide 87

Slide 87 text


 
 export class CartComponent extends Component {} 
 function mapStateToProps(state) {}
 
 function mapDispatchToProps(dispatch) {}
 
 export default connect(mapStateToProps, mapDispatchToProps) (CartComponent); Architecture: Create the Cart cart.jsx

Slide 88

Slide 88 text

import fetch from 'isomorphic-fetch';
 
 export const GET_CART_ITEMS = 'GET_CART_ITEMS';
 
 export function getCartItems() { Architecture: Create the Cart cart-action-creators.es6

Slide 89

Slide 89 text

import fetch from 'isomorphic-fetch';
 
 export const GET_CART_ITEMS = 'GET_CART_ITEMS';
 
 export function getCartItems() {
 return (dispatch) => {
 return fetch('http://localhost:3000/api/user/cart', {
 method: 'GET'
 }) Architecture: Create the Cart cart-action-creators.es6

Slide 90

Slide 90 text

import fetch from 'isomorphic-fetch';
 
 export const GET_CART_ITEMS = 'GET_CART_ITEMS';
 
 export function getCartItems() {
 return (dispatch) => {
 return fetch('http://localhost:3000/api/user/cart', {
 method: 'GET'
 }) Architecture: Create the Cart cart-action-creators.es6 .then((response) => {
 return response.json().then((data) => {
 return dispatch({
 type: GET_CART_ITEMS,
 data: data.items
 });
 });
 })

Slide 91

Slide 91 text

Architecture: Create the Cart import { GET_CART_ITEMS } from './cart-action-creators.es6';
 
 export default function cart(state = {}, action) {
 switch (action.type) {
 case GET_CART_ITEMS:
 return {
 ...state,
 items: action.data
 };
 }
 } cart-reducer.es6

Slide 92

Slide 92 text

Architecture: Create the Cart import { GET_CART_ITEMS } from './cart-action-creators.es6';
 
 export default function cart(state = {}, action) {
 switch (action.type) {
 case GET_CART_ITEMS:
 return {
 ...state,
 items: action.data
 };
 }
 } import { GET_CART_ITEMS } from './cart-action-creators.es6';
 
 export default function cart(state = {}, action) {
 switch (action.type) {
 case GET_CART_ITEMS:
 return {
 ...state,
 items: action.data
 };
 }
 } cart-reducer.es6

Slide 93

Slide 93 text

Server setup Architecture

Slide 94

Slide 94 text

HTML output from server Architecture: Server setup

Slide 95

Slide 95 text

Architecture: Server setup export default function renderView(req, res, next) {
 const matchOpts = {};
 const handleMatchResult = (error, redirectLocation, renderProps) => {
 if (!error && !redirectLocation && renderProps) {
 const store = initRedux();
 let actions = renderProps.components.map((component) => {
 // return actions from loadData() on each component
 }); renderView.jsx

Slide 96

Slide 96 text

Architecture: Server setup 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 renderView.jsx

Slide 97

Slide 97 text

Architecture: Server setup 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 const stringifiedServerState = JSON.stringify(serverState); 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 renderView.jsx

Slide 98

Slide 98 text

Architecture: Server setup 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 const stringifiedServerState = JSON.stringify(serverState); 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 
 const app = renderToString(
 
 
 
 ); const stringifiedServerState = JSON.stringify(serverState); renderView.jsx

Slide 99

Slide 99 text

Architecture: Server setup 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 const stringifiedServerState = JSON.stringify(serverState); 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 
 const app = renderToString(
 
 
 
 ); const stringifiedServerState = JSON.stringify(serverState); 
 const html = renderToString(
 
 );
 
 
 const app = renderToString(
 
 
 
 ); renderView.jsx

Slide 100

Slide 100 text

Architecture: Server setup 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 const stringifiedServerState = JSON.stringify(serverState); 
 Promise.all(promises).then(() => {
 const serverState = store.getState();
 
 
 const app = renderToString(
 
 
 
 ); const stringifiedServerState = JSON.stringify(serverState); 
 const html = renderToString(
 
 );
 
 return res.send(`${html}`); 
 const app = renderToString(
 
 
 
 ); 
 const html = renderToString(
 
 );
 
 renderView.jsx

Slide 101

Slide 101 text

Architecture: Server setup const HTML = (props) => {
 return (
 
 
 
 
 
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; html.jsx

Slide 102

Slide 102 text

Architecture: Server setup const HTML = (props) => {
 return (
 
 
 
 
 
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; html.jsx

Slide 103

Slide 103 text

Architecture: Server setup const HTML = (props) => {
 return (
 
 
 
 
 
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; html.jsx

Slide 104

Slide 104 text

Architecture: Server setup const HTML = (props) => {
 return (
 
 
 
 
 
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; const HTML = (props) => {
 return (
 <html lang="en">
 <head></head>
 <body>
 <div
 id="react-content"
 dangerouslySetInnerHTML={{ __html: props.html }}/>
 <script dangerouslySetInnerHTML={{
 __html: `
 window.__SERIALIZED_STATE__ =
 JSON.stringify(${props.serverState})
 `
 }}/>
 <script type="application/javascript" src="/browser.js" />
 </body>
 </html>
 );
 }; html.jsx

Slide 105

Slide 105 text

Clicking the button results doesn’t do anything Architecture: Server Setup

Slide 106

Slide 106 text

App State available in the browser. Architecture: Server setup

Slide 107

Slide 107 text

Browser setup Architecture

Slide 108

Slide 108 text


 const initialState = JSON.parse(window.__SERIALIZED_STATE__);
 const store = initRedux(initialState);
 Architecture: Browser setup

Slide 109

Slide 109 text


 const initialState = JSON.parse(window.__SERIALIZED_STATE__);
 const store = initRedux(initialState);
 Architecture: Browser setup 
 function init() {
 ReactDOM.render(
 
 
 , document.getElementById('react-content'));
 }
 
 init(); 
 const initialState = JSON.parse(window.__SERIALIZED_STATE__);
 const store = initRedux(initialState);


Slide 110

Slide 110 text

Architecture: Browser setup

Slide 111

Slide 111 text

Challenges and Tradeoffs

Slide 112

Slide 112 text

Competing envs Challenges and Tradeoffs

Slide 113

Slide 113 text

Challenges and Tradeoffs

Slide 114

Slide 114 text

Challenges and Tradeoffs if (process.env.BROWSER) {
 // browser only code
 }

Slide 115

Slide 115 text

Performance Challenges and Tradeoffs

Slide 116

Slide 116 text

Server Performance Strategies Only render above the fold content on the server (SEO tradeoff) Use streams Caching Challenges and Tradeoffs

Slide 117

Slide 117 text

Caching Challenges and Tradeoffs

Slide 118

Slide 118 text

Challenges and Tradeoffs

Slide 119

Slide 119 text

Challenges and Tradeoffs

Slide 120

Slide 120 text

Challenges and Tradeoffs

Slide 121

Slide 121 text

Two Options In memory caching CDN/Edge caching Walmart Labs Server Side Render Cache (Electrode) Challenges and Tradeoffs

Slide 122

Slide 122 text

Complexity Challenges and Tradeoffs

Slide 123

Slide 123 text

Building Universal Apps requires you to think about your application flow in a new way.