Works at RedMart
redmart.com
JavaScript code which is environment agnostic.
https://medium.com/@mjackson/universal-javascript-4761051b7ae9
JavaScript code which is environment agnostic.
That any given line of code (with notable exceptions) can execute both on the client and the server
https://medium.com/@mjackson/universal-javascript-4761051b7ae9
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Some Single Page Application</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="root"></div>
<script src="/app.js"></script>
</body>
</html>
How bots see our javascript applications
# Pre-build the site for robots
if ($http_user_agent ~* "(alltheweb|baidu|bingbot|google|googlebot|msnbot|slurp|facebookexternalhit)") {
rewrite ^ $request_uri break;
expires 24h;
add_header Pragma public;
add_header Cache-Control 'max-age=86400, must-revalidate';
# Reverse proxy to server running PhantomJs
proxy_pass http://$render_services_host:8008;
}
Hacks that we have to do to please these bots
With Universal JavaScript approach we can just render the same application on server
<html>
<head>
<title>Universal JavaScript</title>
<link rel="stylesheet" media="screen, projection" href="static/style.css">
</head>
<body>
<div id="root">
<div data-reactid=".1hxkpv4740" data-react-checksum="2883365">
<form data-reactid=".1hxkpv4740.0"><input type="text" placeholder="Search for products and brands" data-reactid=".1hxkpv4740.0.0"></form><div data-reactid=".1hxkpv4740.1"></div><div data-reactid=".1hxkpv4740.2"><a class="card" target="_blank" href="https://redmart.com/product/strongbow-apple-cider-gold-16652" data-reactid=".1hxkpv4740.2.0"><p data-reactid=".1hxkpv4740.2.0.0">Strongbow Apple Cider Gold</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/5035766044772_3_1426249296514.jpg" data-reactid=".1hxkpv4740.2.0.1"></a><a class="card" target="_blank" href="https://redmart.com/product/bananas-13925" data-reactid=".1hxkpv4740.2.1"><p data-reactid=".1hxkpv4740.2.1.0">Small Bananas</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/4935850351087_3_1432532094722.jpg" data-reactid=".1hxkpv4740.2.1.1"></a><a class="card" target="_blank" href="https://redmart.com/product/gardenia-finegrain-wholemeal-bread" data-reactid=".1hxkpv4740.2.2"><p data-reactid=".1hxkpv4740.2.2.0">Gardenia Finegrain Wholemeal Bread</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/RM_IMG_11671F.JPG" data-reactid=".1hxkpv4740.2.2.1"></a><a class="card" target="_blank" href="https://redmart.com/product/nestle-natural-set-yogurt-13989" data-reactid=".1hxkpv4740.2.3"><p data-reactid=".1hxkpv4740.2.3.0">Nestle Natural Set Yogurt</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/IMG0051_1411017267823.JPG" data-reactid=".1hxkpv4740.2.3.1"></a><a class="card" target="_blank" href="https://redmart.com/product/perrier-natural-mineral-water" data-reactid=".1hxkpv4740.2.4"><p data-reactid=".1hxkpv4740.2.4.0">Perrier Natural Mineral Water</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/img_7474.jpg" data-reactid=".1hxkpv4740.2.4.1"></a><a class="card" target="_blank" href="https://redmart.com/product/dasani-drinking-water-l-case" data-reactid=".1hxkpv4740.2.5"><p data-reactid=".1hxkpv4740.2.5.0">Dasani Drinking Water - Case</p><img src="https://s3-ap-southeast-1.amazonaws.com/media.redmart.com/newmedia/150x/i/m/Dasani-Drinking-Water-12x15L1401883801960_1420546020455.jpg" data-reactid=".1hxkpv4740.2.5.1"></a></div>
</div>
</div>
<script src="static/bundle.js"></script>
</body>
</html>
Better initial load time
Download HTML (document)
Download JavaScript (app.js)
Makes Ajax calls
Download HTML (document)
Download JavaScript (app.js)
Do other stuff
Client Side Rendering
Server Side Rendering
User sees content
User sees content
Shared logic between server and clients
App.js
Input.js
Progress.js
SearchResult.js
Component hierarchy
App.js
Input.js
Progress.js
SearchResult.js
App.js
import React, { Component } from 'react'
import Input from '../components/Input'
import SearchResult from '../components/SearchResult'
import ResultCard from '../components/ResultCard'
import products from '../data/products'
export default class App extends Component {
// set initial state
constructor (props) {}
// render state
render () {}
// do required logic on search input submit
handleOnSubmit (e) {}
// get products based on input (in this case we will just get some randoms stuff)
getProducts () {}
}
App.js
render () {
const { progress, products } = this.state
const productResultSet = products.map((product) => {
return <ResultCard {...product} />
})
return (
<div>
<Input
placeholder='Search for products and brands'
handleOnSubmit={::this.handleOnSubmit} />
<div>{progress}</div>
<SearchResult>
{productResultSet}
</SearchResult>
</div>
)
}
client.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './containers/App'
ReactDOM.render(
<App />,
document.getElementById('root')
)
index.html
<!doctype html>
<html>
<head>
<title>Universal JavaScript</title>
<link rel="stylesheet" media="screen, projection" href='static/style.css'>
</head>
<body>
<div id='root'></div>
<script src='static/bundle.js'></script>
</body>
</html>
server.js
import express from 'express'
import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import App from './containers/App'
import htmlWrapper from './Html'
const server = express()
server.use('/static', express.static(__dirname + '/../static'));
server.use((req, res) => {
res.send(htmlWrapper(renderToString(<App />)))
})
server.listen(3000)
Maintaining State between server and client
// Store created on Server
let store = createStore(AppReducers)
// Dehydrating for client
const getCurrentState = store.getState()
// Somewhere in our html
<script>
window.STATE_FROM_SERVER = ${JSON.stringify(getCurrentState)}
</script>
// Rehydrating on client
let store = createStore(AppReducers, window.STATE_FROM_SERVER)
Modules/Libraries that work both on Server and Client
// DON'T DO THIS
if (__SERVER__) {
http.request(options, processData)
} else {
$.ajax(options).done(processData)
}
// TRY universal/isomorphic modules
import fetch from 'isomorphic-fetch'
fetch(url).then(processData)
Effectively handling data fetching
// For super simple apps, fetch and
// setState just before generating html
fetchSearchTermResult(query, function (json) {
// create new state after data fetch
const newState = {
...getStore().getState(),
loading: false,
loaded: true,
query: query,
payload: json
}
// send down the response with new state
res.send(
generateInitialHtml(
getStore(newState),
getStore(newState).getState(),
renderProps
)
)
})
Pre Fetch
Fetch all the page data on server before sending down the html
Effectively handling data fetching
Pre Fetch
Fetch all the page data on server before sending down the html
// Handler Component
export default class Landing extends Component {
static fetchData (store) {
return store.dispatch(fetchBothCategoriesAndProductList())
}
}
// dataFetchMiddleware loops through all the components,
// looks for all fetchData and resolve those promises
const createStoreWithMiddleware = applyMiddleware(
dataFetchMiddleware
)(createStore)
let store = createStoreWithMiddleware(reducers, initialState)
Effectively handling data fetching
Deferred Fetch
Pre Fetch
// Handler Component
export default class Landing extends Component {
static fetchData (store) {
return store.dispatch(fetchCategories())
}
componentDidMount () {
store.dispatch(fetchProductList())
}
}
// dataFetchMiddleware loops through all the components,
// looks for all fetchData and resolve those promises
const createStoreWithMiddleware = applyMiddleware(
dataFetchMiddleware
)(createStore)
let store = createStoreWithMiddleware(reducers, initialState)
Effectively handling data fetching
Deferred Fetch
Pre Fetch
// Handler Component with react-fetcher + some promise middleware for redux
@prefetch(({ dispatch }) => dispatch(fetchCategories())
@defer(({ dispatch }) => dispatch(fetchProductList())
export default class Landing extends Component {
// Just render data
}