|
import DocumentTitle from 'react-document-title' |
|
import Html from './html' |
|
import MobileDetect from 'mobile-detect' |
|
import Promise from 'bluebird' |
|
import React from 'react' |
|
import Router from 'react-router' |
|
import Transmit from 'react-transmit' |
|
import config from './config' |
|
import initialState from './initialstate' |
|
import renderToString from './react-transmit/render_to_string' |
|
import routes from '../client/routes' |
|
import {state} from '../client/state' |
|
import {storeCookies} from '../client/request_store' |
|
|
|
export default function render(req, res, locale) { |
|
const device = new MobileDetect(req.headers['user-agent']).phone() ? 'mobile' : 'desktop' |
|
const path = req.path |
|
|
|
return renderPage(req, res, initialState, path, device) |
|
} |
|
|
|
// This function sets up the state before first and second round of React.renderToString |
|
// inside of Transmit.renderToString |
|
function beforeRender(req, appState) { |
|
return function beforeRender() { |
|
state.load(appState) |
|
storeCookies(req) |
|
} |
|
} |
|
|
|
// TODO: Refactor. |
|
function renderPage(req, res, appState, path, device) { |
|
return new Promise((resolve, reject) => { |
|
const router = Router.create({ |
|
routes, |
|
location: path, |
|
onError: reject, |
|
onAbort: (abortReason) => { |
|
if (abortReason.constructor.name === 'Redirect') { |
|
const {to, params, query} = abortReason |
|
const path = router.makePath(to, params, query) |
|
res.redirect(path) |
|
resolve() |
|
return |
|
} |
|
reject(abortReason) |
|
} |
|
}) |
|
router.run((Handler, routerState) => { |
|
const notFound = routerState.routes.some(route => route.name === 'not-found') |
|
const status = notFound ? 404 : 200 |
|
|
|
// getPageHtml now returns a Promise |
|
getPageHtml(Handler, appState, device, routerState.routes, beforeRender) |
|
.then(html => { |
|
res.status(status).send(html) |
|
resolve() |
|
}).catch(error => { |
|
console.error('getPageHTML error', error) |
|
reject(error) |
|
}) |
|
}) |
|
}) |
|
} |
|
|
|
function getPageHtml(Handler, appState, device, matchedRoutes, beforeRender) { |
|
// Important! We need to send the matchedRoute as queryParam to the main component |
|
return renderToString(Handler, beforeRender, {queryParams: {matchedRoutes}}).then(result => { |
|
const {reactString, reactData} = result |
|
const appHtml = `<div id="single-page-app"><div id="react-root">${reactString}</div></div>` |
|
const appScriptSrc = config.env.isProduction |
|
? '/app.js?v=' + config.version |
|
: '//localhost:8888/build/public/app.js' |
|
|
|
let scriptHtml = ` |
|
<script> |
|
(function() { |
|
window._appState = ${JSON.stringify(appState)}; |
|
var app = document.createElement('script'); app.type = 'text/javascript'; app.async = true; |
|
var src = '${appScriptSrc}'; |
|
// IE<11 and Safari need Intl polyfill. |
|
if (!window.Intl) src = src.replace('.js', 'intl.js'); |
|
app.src = src; |
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(app, s); |
|
})() |
|
</script>` |
|
|
|
if (config.env.isProduction && config.googleAnalyticsId !== 'UA-XXXXXXX-X') |
|
scriptHtml += ` |
|
<script> |
|
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= |
|
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; |
|
e=o.createElement(i);r=o.getElementsByTagName(i)[0]; |
|
e.src='//www.google-analytics.com/analytics.js'; |
|
r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); |
|
ga('create','${config.googleAnalyticsId}');ga('send','pageview'); |
|
</script>` |
|
|
|
const title = DocumentTitle.rewind() |
|
|
|
const outputHtml = '<!DOCTYPE html>' + React.renderToStaticMarkup( |
|
<Html |
|
bodyHtml={appHtml + scriptHtml} |
|
isProduction={config.env.isProduction} |
|
title={title} |
|
version={config.version} |
|
device={device} |
|
/> |
|
) |
|
|
|
// This adds window.__reactTransmitPacket with results of the queries |
|
// so that client does not need to perform the same queries again |
|
return Transmit.injectIntoMarkup(outputHtml, reactData) |
|
}) |
|
} |
I found that adding @Flux(store) on a root "App" component will conflict somehow with react-transmit and queried data is not available to be rendered on server. Removing this decorator makes it all work.
Do you have any ideas? I'm kind of new to Node, so I will work on this to fix it, but any advice is greatly appreciated!