Skip to content

Instantly share code, notes, and snippets.

@coopermaruyama
Last active July 19, 2017 03:37
Show Gist options
  • Save coopermaruyama/1299eab5cfd18acdcc2c424ec8988b86 to your computer and use it in GitHub Desktop.
Save coopermaruyama/1299eab5cfd18acdcc2c424ec8988b86 to your computer and use it in GitHub Desktop.
Rendering
/**
* Dynamic content is served via this middleware. Each page has a 'path'
* attribute which determines the path on which it should render. Any time a
* request is made to the server, we check the requested path to see if any
* page has that same path, in which case we step in and render that page.
* @flow
*/
/* eslint-disable react/no-danger, no-inline-comments */
import type { $Request, $Response } from 'express';
import React from 'react';
import { SheetsRegistryProvider, SheetsRegistry } from 'react-jss';
import Page from '~/models/page';
import ReactDOMServer from 'react-dom/server';
import Templates from 'shared/templates';
import Slot from 'shared/components/Slot';
/**
* The primary middleware. Essentially checks the path of a request and if it
* matches a CMS page, renders it.
*/
const cms = (req: $Request, res: $Response, next: Function) => {
Page.findOne({
path: req.path
}).exec().then((page) => {
if (page) {
const html = render(page._doc);
return res.send(html);
}
return next();
});
};
/**
* Handles the rendering of the page. Implements React server-side rendering,
* and injects script tags for the template and widgets it will need.
*/
function render(page) {
// On the server, react-jss needs this as a target to push styles to.
const sheets = new SheetsRegistry();
// Get the paths to the widgets which the page depends on.
const widgetPaths = getRequiredWidgetPaths(page);
// templateId matches the right directory in shared/templates/<templateId>
const templateId = page.template.id;
// `Templates` contains all the templates, get the one we need to render.
const Template = Templates[templateId];
const props = {
slots: page.slots,
renderer: Slot
};
// This will give us the server-side rendered HTML for our page.
const html = ReactDOMServer.renderToString(
<SheetsRegistryProvider registry={sheets}>
<Template {...props} />
</SheetsRegistryProvider>
);
return ReactDOMServer.renderToString(
<html lang="en-US" style={{ margin: 0 }}>
<head>
<link href="/styles.css" type="text/css" rel="stylesheet" />
{/* Inject the stylesheet from react-jss */}
<style type="text/css">
{sheets.toString()}
</style>
</head>
<body style={{ margin: 0 }}>
{/* Our server-side rendered HTML */}
<div
id="content"
dangerouslySetInnerHTML={{ __html: html }}
/>
{/* Load React from CDN for speed and so we dont need to bundle it */}
<script
src="//cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"
/>
<script
src="//cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"
/>
{/* Load each widget this page depends on */}
{widgetPaths.map(path => <script src={path} key={path} />)}
{/* And the template, too */}
<script src={`/dist/templates/${templateId}.js`} />
{/* And finally, initalize React on client-side */}
<script
dangerouslySetInnerHTML={{
__html: `
var props = ${JSON.stringify(props)};
ReactDOM.render(
React.createElement(${templateId}, props),
document.getElementById('content')
);
`
}}
/>
</body>
</html>
);
}
/**
* Parses every widget used by a page and returns a unique list of paths to
* each widget used by the page.
*/
function getRequiredWidgetPaths(page) {
const requiredWidgets = [];
page.slots.forEach((slot) => {
slot.components.forEach((component) => {
if (requiredWidgets.indexOf(component.componentId) === -1) {
requiredWidgets.push(component.componentId);
}
});
});
return requiredWidgets.map(w => `/dist/widgets/${w}.js`);
}
function getDefaultStyles() {
return {
'html, body': {
margin: 0
}
};
}
export default cms;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment