React and Server Side Rendering (SSR)

Gagandeep Singh
3 min readFeb 14, 2019

--

Not for the faint of heart i tell you.

Server side rendering with React

But, if you are interested in the benefits of SSR (namely SEO and faster load), and don’t want to revamp your code completely (because you will) to get things like window, document working (Node doesn’t know about them, surprise !) and even more basic patterns like async await (shocker !) working with babel, here’s a little hack for you.

The idea behind SSR is this:

  • You don’t want a JS only app. You need some html so crawlers can figure out what your website’s all about -namely static content
  • When the browser requests for a page from your website (GET /home), your server needs to return some html
  • Subsequent interactions with the page (unless you reload / directly open another page in a new tab) are handled via JS (React’s bundle.js).

It looks something like this:

import Express from 'express';
import React from 'react';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import {reducerFn} from './reducers';
import {renderToString} from 'react-dom/server';
import {routes} from './app';
import {StaticRouter} from "react-router-dom";
import {Helmet} from "react-helmet";


const app = Express();
const port = 8092;

// Serve static files
app.use("/assets", Express.static('assets'));
app.use("/static", Express.static('static'));

// This is fired every time the server side receives a request
app.use(handleRender);


// We are going to fill these out in the sections to follow
function handleRender(req, res) {
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
console.log('fullUrl: ', fullUrl);
console.log('req.url: ', req.url);

// Create a new Redux store instance
const store = createStore(reducerFn);

// Render the component to a string
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
{routes}
</StaticRouter>
</Provider>
);
const helmet = Helmet.renderStatic();

// Grab the initial state from our Redux store
const preloadedState = store.getState();

// Send the rendered page back to the client
res.send(renderFullPage(helmet, html, preloadedState));
}

function renderFullPage(helmet, html, preloadedState) {
return `
<!doctype html>
<html>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
${helmet.style.toString()}
${helmet.script.toString()}

<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="icon" href="https://imageserver.homedruid.com/v1/image/get?id=1398" type="image/png" />

<script type="text/javascript" async
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB_6Ly7ovRtYqb_p7QxxRSV3WnQh3b1e6Y&libraries=places"></script>
</head>
<body style="margin: 0">
<div id="root">${html}</div>\n
<script>
// WARNING: See the following for security issues around embedding JSON in HTML:
// http://redux.js.org/recipes/ServerRendering.html#security-considerations
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
<script src="/assets/bundle.js"></script>
</body>
</html>
`
;
}


app.listen(port);

Look at the magic below:

// Magic 1
renderToString()
// Magic 2
<script src="/assets/bundle.js"></script>

We’re using Node as a browser to execute JS and render the layout as html and serve it. We are also sending in the bundle.js code (our SPA) which is going to overwrite the rendered html layout, and render the components in JS again. Hadn’t thought this when I started, but it’s quite simple and effective. The major pain in SSR is really getting these 2 bits of magic working.

So here’s the hack. If you can’t get SSR to work because its just too painful, do the following. Write a simple nginx / Express server that serves out static html (you can get this by rendering the SPA and getting document.innerHTML) for the homepage, along with bundle.js.

And thats it !

Wanna see it in action: https://www.heloprotocol.in/

--

--

Gagandeep Singh

Ex-Google, Ex-Ola. Startup founder @HeloProtocol. Math lover, always looking to learn something new.