Created
March 21, 2019 14:20
-
-
Save ithinkdancan/ac70e03071623260279110e6cdb85c05 to your computer and use it in GitHub Desktop.
Server Rendering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import url from 'url'; | |
import { get, includes, isNumber, omit } from 'lodash'; | |
import { bindActionCreators } from 'redux'; | |
import { matchPath } from 'react-router-dom'; | |
import { flushChunkNames } from 'react-universal-component/server'; | |
import flushChunks from 'webpack-flush-chunks'; | |
import BodyClassName from 'react-body-classname'; | |
import Helmet from 'react-helmet'; | |
import uuidv4 from 'uuid/v4'; | |
import createStore from 'store/create'; | |
import { getDefaultPage } from 'store/modules/pages'; | |
import { staticRoutes } from 'routes'; | |
import { initialState as initialAppState, serverRenderError } from 'store/modules/app'; | |
import { initialState as initialUserState } from 'store/modules/user'; | |
import applicationRenderer from './lib/render/application'; | |
import pageRenderer from './lib/render/page'; | |
export default ({ clientStats, config, rollbar }) => ( | |
(req, res, next) => { | |
const currentUrl = url.parse(req.originalUrl); | |
// Skip the health endpoint until it is created | |
if (includes(['/health'], currentUrl.pathname)) { | |
next(); | |
return; | |
} | |
// Create the store | |
const store = createStore({ | |
user: { | |
...initialUserState, | |
siteId: req.siteId, | |
visitId: req.query.SELENIUM ? 'SELENIUM' : (req.cookies.visit || req.visitId), | |
gcid: req.cookies.gcid, | |
user_ip: get(req, 'user_ip', null), | |
traffic_source_id: get(req, 'traffic_source_id', null), | |
cid: get(req, 'cid', null), | |
ctid: get(req, 'ctid', null) | |
}, | |
app: { | |
...initialAppState, | |
ssag_host: config.ssag.external, | |
ssag_host_internal: config.ssag.internal, | |
baseUrl: config.webserver.baseUrl, | |
trackingURL: config.tracking.url, | |
trackingId: uuidv4(), | |
segmentWriteKey: config.segment.writeKey, | |
googleMapsKey: config.google.mapsKey, | |
recaptchaSiteKey: config.recaptcha.siteKey, | |
rollbarKey: config.rollbar.key, | |
environment: config.env | |
} | |
}, { rollbar, request: req }); | |
// Find the matching route(s) | |
const branch = staticRoutes.reduce((branches, route) => { | |
const match = matchPath(currentUrl.pathname, { | |
path: route.path || '', | |
exact: true, | |
strict: true | |
}); | |
return match ? [...branches, { route, match }] : branches; | |
}, []); | |
// Run through any matching routes to fetch their data | |
const routeData = branch | |
.filter(({ route }) => route.loadData) | |
.map(({ route, match }) => { | |
const actions = bindActionCreators(route.actions || {}, store.dispatch); | |
return route.loadData({ | |
actions, | |
route, | |
match, | |
queryParams: req.query | |
}); | |
}); | |
// Get the default page data (Phone, Experiments) | |
const defaultPageData = store.dispatch(getDefaultPage()); | |
const resolveData = Promise.all([defaultPageData, ...routeData]); | |
// Render the Application against the resolved data | |
const renderApplication = () => { | |
const state = store.getState(); | |
// Render the application as a string | |
const { applicationString, redirectURL, statusCode } = applicationRenderer({ | |
store, | |
location: currentUrl.path, | |
cookies: req.universalCookies | |
}); | |
// Catch any application redirects before rendering | |
if (includes([301, 302], statusCode) && redirectURL) { | |
res.redirect(statusCode, redirectURL); | |
return; | |
} | |
// Return any status codes returned from the application | |
if (isNumber(statusCode)) { | |
res.status(statusCode); | |
} | |
// Get the rendered styles | |
const chunkNames = flushChunkNames(); | |
const { styles, stylesheets, publicPath, scripts, js } = flushChunks(clientStats, { chunkNames }); | |
// Render the page markup | |
const renderedPage = pageRenderer({ | |
state: omit(state, 'app.ssag_host_internal'), // Application State with internal SSAG url removed | |
bodyClass: BodyClassName.rewind(), // Body classes applied during render | |
head: Helmet.renderStatic(), // Head Tags | |
applicationString, // Rendered React App | |
publicPath, // Client Asset Path | |
stylesheets, // Stylesheet Markup | |
styles, // Stylesheet List | |
scripts, // Scripts to preload | |
js // JS Tag Markup, | |
}); | |
res.send(renderedPage); | |
}; | |
resolveData | |
.then(renderApplication) | |
.catch((error) => { | |
// If an exception was thrown during the render, update the status and re-render | |
const errorMessage = config.env === 'production' ? error.message : error.stack; | |
store.dispatch(serverRenderError(errorMessage)); | |
renderApplication(); | |
rollbar.error(error, req); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment