Universal Studios - Server-Side Rendering With React Is A Marvel!

You might also like

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 73

:

Server-Side Rendering with


React is a !
Tomer Ohana & Gil Tayar, September 2016

@giltayar
@tomerdoesntliketwitter
Server-side rendering - what is it good for?
The Dawn of Computing...
A History of Clients
In the beginning...
… was the mainframe
The Mainframe Terminal
1. sends a page

Architecture of a Mainframe terminal


1. sends a page

2. Interacts with page

Architecture of a Mainframe terminal


1. sends a page
3. sends input values

2. Interacts with page 4. Runs batch to


interact with page
Architecture of a Mainframe terminal
1. sends a page
3. sends input values
5. returns page

2. Interacts with page 4. Runs batch to


interact with page
Architecture of a Mainframe terminal
The Mini-Computer
Characters
Keystrokes

The Mini-Computer
The Personal Computer (PC)
Queries & Commands

Client/Server
The Internet and the World Wide Web
1. sends a page
3. sends input values
5. returns page

2. Interacts with page 4. Runs batch to


interact with page
Architecture of a Mainframe terminal
1. sends a page
3. sends input values
5. returns page

2. Interacts with page 4. Runs batch to


interact with page
Architecture of a Mainframe terminal
i on
l at
i pu
an
M
O M
D
s t
ue
eq
tp R
H t
M L
X
Page/Response Interaction
mainframe
Browsers pre-Ajax Era

plus ça change, plus c'est la même chose


Page/Response Interaction
mainframe
Browsers pre-Ajax Era

Granular Interaction
mini

Browsers Ajax Era

plus ça change, plus c'est la même chose


Page/Response Interaction
mainframe
Browsers pre-Ajax Era

Granular Interaction
mini

Browsers Ajax Era

Client/Server
PC

plus ça change, plus c'est la même chose


Page/Response Interaction
mainframe
Browsers pre-Ajax Era

Granular Interaction
mini

Browsers Ajax Era

Client/Server
PC

plus ça change, plus c'est la même chose


The Rise of Client-Side Frameworks
ols
To Dojo
oo
M
er y
u
jQ The Rise of Client-Side Frameworks

Pro
tot
ype Angular 1
Server vs Client Languages
Server Client
● C++ ● JavaScript
● Java
● C#
● Python
● Ruby
● Scala
● Erlang
● Haskell
● Clojure
NodeJS
The Rise of the Isomorphic Frameworks
The Rise of the Isomorphic Frameworks
Code
Code
CommonJS
Code
Module

Same code, different build processes


ReactJS
hi c Code
rp Code
CommonJS
Code
mo Module Un
o
Is de Ap iver
Co ps sa
l

Universal Apps
What’s So Bad About Client-Side
Rendering?
TTF
E O R
S
What’s So Bad About Client-Side
Rendering?
SEO
Search Engine Optimization
TTFR
Time To First Render

1. Get HTML

2. Get JS
3. Run JS
4. Get Data
5. Render Data
The Seven Stages Towards Full Server-
side Rendering
#1. client-side-react-rendering
app.js
const express = require('express')

const app = express()

app.use('/index.html',
express.static(__dirname + '/src/public/index.html'))
app.use('/bundle.js',
express.static(__dirname + '/lib/public/bundle.js'))

app.listen(process.env.PORT || 3000)
public/index.html

<html>
<body>
<div id="root" />
<script src="bundle.js"></script>
</body>
</html>
public/app.jsx

const ReactDOM = require('react-dom')


const helloWorld = require('./hello.jsx')

ReactDOM.render(helloWorld,
document.getElementById('root'))
public/hello.jsx

const React = require('react')

module.exports = <div>hello world</div>


package.json
{
"name": "universal-studios",
"scripts": {
"build": "webpack",
"start": "node server.js"
},
"devDependencies": {...},
"dependencies": {...}
}
webpack.config.js
module.exports = {
entry: "./src/public/app.jsx",
output: {
path: __dirname + "/lib/public", filename: "bundle.js"
},

module: { loaders: [{
loader: 'babel-loader',
test: /\.jsx?$/,
include: __dirname + "/src"
}]}
};
.babelrc

{
"presets": ["es2015", "react"]
}
#2. isomorphic-react-rendering
app.js
const express = require('express')
const ReactDomServer = require('react-dom/server')
const helloWorld = require('./src/public/hello.jsx')

const app = express()

app.use('/index.html', (req, res) => res.send(`


<html><body>
<div id="root">${ReactDomServer.renderToString(helloWorld)}</div>
<script src="bundle.js"></script>
</body></html>
`))
public/app.jsx

const ReactDOM = require('react-dom')


const helloWorld = require('./hello.jsx')

ReactDOM.render(helloWorld,
document.getElementById('root'))
#3. stateful-react-app
public/counter.jsx
module.exports = class Counter extends React.Component {
constructor(props) {
this.state = {value: 0}
}
render() {
const {value} = this.state;
return (<div>
<button onClick={() => this.setState({ value: value - 1 }) }>-</button>
<span>{value}</span>
<button onClick={() => this.setState({ value: value + 1 }) }>+</button>
</div>)
}
}
public/app.jsx &app.js
...
const Counter = require('./counter.jsx')

ReactDOM.render(<Counter />, document.getElementById('root'))

app.use('/index.html', (req, res) => res.send(`


<html><body>

<Counter/>)}</div>
<div id="root">${ReactDomServer.renderToString(
<script src="bundle.js"></script>
</body></html>`))
#4. state-in-redux
public/app.jsx
...
const rootFactory = require('./Root.jsx')
const {createStore} = require('./createStore.jsx')

const initialState = 0
const store = createStore(initialState)
const Root = rootFactory(store)

ReactDOM.render(<Root />, document.getElementById('root'))


app.js
app.use('/index.html', (req, res) => {
const initialState = 0
const store = createStore(initialState)
const Root = rootFactory(store)

res.send(`
<html><body>
<div id="root">${ReactDomServer.renderToString(<Root/>)}</div>
<script src="bundle.js"></script>
</body></html>`)
})
public/createStore.jsx
const {createStore} = require('redux')

function counterReducer(state = 0, action) {


switch (action.type) {
case 'INC': return state + 1
case 'DEC': return state - 1
default: return state
}
}
exports.createStore = function(initialState) {
return createStore(counterReducer, initialState)
}
public/counter.jsx
class Counter extends React.Component {
render() {
const {value} = this.props;
return (<div>
<button onClick={() => this.props.dispatch({ type: 'DEC' }) }>-</button>
<span>{value}</span>
<button onClick={() => this.props.dispatch({ type: 'INC' }) }>+</button>
</div>)
}
}
function mapStateToProps(state) {return {value: state}}
module.exports = connect(mapStateToProps)(Counter);
#5. passing-state
#5. passing-state
app.js
app.use('/index.html', (req, res) => {

const initialState = parseInt(req.query['start-from'] || '0')


const store = createStore(initialState)
const Root = rootFactory(store)

res.send(`
<html><body>
<div id="root">${ReactDomServer.renderToString(<Root/>)}</div>
<script src="bundle.js"></script>
</body></html>`)
})
app.js
app.use('/index.html', (req, res) => {
const initialState = parseInt(req.query['start-from'] || '0')
const store = createStore(initialState)
const Root = rootFactory(store)

res.send(`
<html><body>
<div id="root">${ReactDomServer.renderToString(<Root/>)}</div>

<script>window.__INITIAL_STATE__ =

${JSON.stringify(store.getState())}</script>
<script src="bundle.js"></script>
public/app.jsx

const initialState = window.__INITIAL_STATE__


delete window.__INITIAL_STATE__
const store = createStore(initialState)
const Root = rootFactory(store)

ReactDOM.render(<Root />, document.getElementById('root'))


#6. server-persisted-state
app.js
app.use('/index.html', (req, res) => {
const initialState =

parseInt(fs.readFileSync('data.txt', {encoding: 'UTF-8'}))


const store = createStore(initialState)
const Root = rootFactory(store)

res.send(`
<html><body>
<div id="root">${ReactDomServer.renderToString(<Root/>)}</div>
<script src="bundle.js"></script>
</body></html>`)
})
public/counter.jsx
class Counter extends React.Component {
render() {
const {value} = this.props;
return (<div>
<button onClick={() => this.props.dispatch({ type: 'DEC' }) }>-</button>
<span>{value}</span>
<button onClick={() => this.props.dispatch({ type: 'INC' }) }>+</button>
<button onClick={() => doSave(value)}>Save</button>
</div>)
}
}
const doSave = (value) => fetch('/save?value=' + value, {method: 'POST'})
app.js

app.use('/save', (req, res) => {


fs.writeFileSync('data.txt',
req.query['value'].toString())

res.end()
})
#7. universal-app
public/counter.jsx
class Counter extends React.Component {
render() {
const {value} = this.props;
return (<div>
<button onClick={() => this.props.dispatch({ type: 'DEC' }) }>-</button>
<span>{value}</span>
<button onClick={() => this.props.dispatch({ type: 'INC' }) }>+</button>
<button onClick={() => doSave(value)}>Save</button>
<button onClick={() => dispatch(doRefresh())}>Refresh</button>
</div>)
}
}
public/refresher.js
const fetch = require('isomorphic-fetch')

module.exports = (host) =>


() => (dispatch) => {
return fetch(`http://${host}/data`)
.then(response => response.text())
.then(value =>
dispatch({type: 'SET', value: parseInt(value, 10)}))
}
app.js
app.use('/index.html', (req, res) => {
const store = createStore()
const Root = rootFactory(store, doRefreshFactory('localhost:3000'))

store.dispatch(doRefresh()).then(() => {
res.send(`
<html><body>
<div id="root">${ReactDomServer.renderToString(<Root/>)}</div>
<script src="bundle.js"></script>
</body></html>`)
})
})
public/app.jsx
...
const doRefreshFactory = require('./refresher')

const initialState = window.__INITIAL_STATE__


const store = createStore(initialState)
const Root = rootFactory(store,
doRefreshFactory('localhost:3000'))

ReactDOM.render(<Root />, document.getElementById('root'))


Summary
● It’s really easy.
● Isomorphic code is as easy as using CommonJS modules + webpack + babel
● Just use ReactDOMServer to render to string, using same code as in client
● Read the state in the server, pass it to React, and render
● Then pass it to the client, using window.__INITITAL_STATE_ trick, and have the
client do the same
● And get Mainframe level performance!
Thank you
https://github.com/giltayar/universal-studios
@giltayar
tomero@wix.com

You might also like