Skip to content

Instantly share code, notes, and snippets.

@normancarcamo
Created October 6, 2017 10:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save normancarcamo/cd1fc5b96e98c8639665bd661da96c61 to your computer and use it in GitHub Desktop.
Save normancarcamo/cd1fc5b96e98c8639665bd661da96c61 to your computer and use it in GitHub Desktop.
React Server Rendering with Universal Request
import React from 'react'
import { Router } from 'express'
import routes from 'shared/routes'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import cheerio from 'cheerio'
import { StaticRouter } from 'react-router'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
function getTemplate() {
try {
return readFileSync(resolve(process.cwd(), 'build/public/index.html'), { encoding: 'UTF-8' })
} catch (e) {
throw e
}
}
function avoidXSS(props) {
return JSON.stringify(props).replace(/<\/script/g, '<\\/script').replace(/<!--/g, '<\\!--');
}
function addComponentToTemplate({ props, html }) {
const content = getTemplate()
let $ = cheerio.load(content)
$('title').text(props.title || 'Untitled');
$('head').append(`<script id="__initial_state__">window.__INITIAL_STATE__ = ${avoidXSS(props)};</script>`);
$(`#root`).html(html);
return $.html()
}
function getRoute(routes, path) {
let route_ = null
function get(_path, _routes) {
_routes.some(route => {
if ('path' in route && route.path === _path) {
route_ = route
return true
} else {
if ('routes' in route) {
get(_path, route.routes)
}
return false
}
})
}
get(path, routes);
return route_;
}
function renderComponent(Component, props, url) {
let context = {}
let content = (
<StaticRouter location={url} context={context}>
<Component {...props} />
</StaticRouter>
)
if (process.env.NODE_ENV === 'production') {
const htmlComponent = renderToStaticMarkup(content)
return {
html: addComponentToTemplate({ html: htmlComponent, props: props }),
context: context
};
} else {
const htmlComponent = renderToString(content)
return {
html: addComponentToTemplate({ html: htmlComponent, props: props }),
context: context
};
}
}
function render(req, res, props = {}) {
const { html, context } = renderComponent(routes[0].component, props, req.url)
if (context.url) {
res.redirect(context.status, context.url)
} else if (context.status) {
res.status(context.status).send(html)
} else {
res.send(html)
}
}
const router = Router()
router.get('*', async (req, res) => {
const route = getRoute(routes, req.originalUrl)
if (route && route.loadData) {
try {
const response = await route.loadData()
render(req, res, { title: 'My Project', data: response.body })
} catch(error) {
render(req, res, { title: 'My Project', error: error.response.body })
}
} else {
render(req, res, { title: 'Boilerplate' })
}
})
export default router
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment