Down the Universal Rabbit Hole

For the last month, I’ve been exploring what it takes to make Single-Page Applications (SPAs) more efficient; partly for work and partly to satisfy my own curiosity. After hearing about Isomorphic, now Universal Javascript from a colleague I became determined to figure it out. This post is the first of my chronicles in exploring the world of Universal Javascript and Sever-Side Rendering in React.

After numerous searches, I’ve come across a lot of helpful resources; however, in typical Javascript fashion, tutorials quickly become outdated. The most helpful of the tutorials I’ve come across were Mo Binni’s tutorial and a tutorial I found on scotch.io. In particular, I highly recommend looking at the source repositories linked on GitHub. In addition, I also followed the guides on ReactTraining’s website to use React Router v4.

So why study Universal Javascript? In all honesty, my journey started out as an example of fad development — universal javascript is one of the popular buzz words in the web dev industry and I wanted to see what all the big fuss was all about. After lots of reading universal javascript became a prime example of knowing your audience. What I mean about knowing your audience is that universal javascript requires scaling a steep learning curve and depending on what you’re trying to do, it might not even be worth the effort in the first place. Universal javascript software really shines if your business/livelihood depends on Search Engine Optimization (SEO) or if your customers are primarily accessing your web application through devices with limited resources/bandwidth.

With what I said in my previous statement, I began to think about situations in which my livelihood could potentially depend on web crawlers being able to access my web app. The first example that came to mind was a blogging application because blogs are constantly used to raise awareness and generate exposure for the author. Therefore it is pertinent that the blog can be indexed by search engines and web crawlers — making it accessible and allowing new readers/subscribers to discover content.

I’ll hold no illusions of my work so far. I’m nowhere near a blogging platform, but what I do have is a rudimentary understanding of SSR with react which I’m hoping to expand and a project which is just the beginning.

The outset

I’m no stranger to React programs having written a couple for work and a few on side projects. Of all the different client-side libraries, React has worked its way as my go-to and into my heart. I began the blog project by pulling from a skeleton project to make the boilerplate easier.

Packing up for the journey

After cloning my starter project from github, I needed to update my server-side code to render React JSX code. In order to translate JSX into plain Javascript, I need to use Babel. Fortunately with babel-register I can process my server-side code through Babel at runtime.

require('babel-register')({});

Next I have to change how the SPA gets served to the client. Originally, I would simply use the express static middleware to serve files to the client-side, but since this is a server-side application, I have to inject the React code in at runtime. In order to make it so that I send a pre-rendered React page to the client, I need to read in a template from a file, store the template code as a string and inject the rendered React code into the template string.

import React from 'react';
import {renderToString} from 'react-dom';
import {Router} from 'express';
import {StaticRouter} from 'react-router';
import {readFileSync} from 'fs';
import App from './app';
const router = new Router();
// Read in the template from file
let template = readFileSync('template.html', {encoding: 'utf-8'});

router.use('*', (req, res) => {
let status = 200;
const context = {};
  // Inject rendered react code into template string
template = template.replace('{ reactOutput }', renderToString(
<StaticRouter context={context} location={req.url}>
<App />
</StaticRouter>));
  if(context.url){
return res.redirect(context.url);
}
  if(context.is404){
status = 404;
}
  // Serve template string to client as index.html
res.status(status).send(template);
}
...

Then I make changes to my template code:

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div id="root">
{ reactOutput }
</div>
</body
</html>

Note reactOutput that is the part of the template that gets replaced with the rendered code in my * route.

Observations

When running a curl on the running app, I get a fully-formed DOM instead of a html page that has a placeholder stub which won’t render the React components until the browser runs the code that bootstraps React. The curl response is detailed below:

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Skeleton</title>
<link href="app.css" rel="stylesheet"></head>
<body>
<div id="react">
<h1 data-reactroot="" data-reactid="1" data-react-checksum="1953043453"> Hello from React</h1>
</div>
<script type="text/javascript" src="react.27d2f35cac576a21d53b.bundle.js"></script><script type="text/javascript" src="app.bfdd357a5e295eb5c8f4.bundle.js"></script></body>

The h1 tag data-reactroot used to be a loading placeholder stub ‘Loading…

During my investigation into SSR with React, one of the areas I was stumped on is when you have assets that are specific to the server or are specific to the client. After mulling over the problem for a while, I figured out that while the initial render of the page occurs on the server-side, the server-side components will become supplanted with the client-side renders once the client-side bundle is available and has been loaded by the client. Client-side assets such as static images can be provided to the server-side renders by using a library like webpack-isomorphic-tools. An assumption I’m making is that images can be rendered server-side via data:// urls.

Kind of related to the last paragraph, a question that I have still outstanding is the difference between server-side routing in React vs client-side routing in React. Recently ReactTraining released v4 of the react-router library which change the routing scheme from a static routing config to a declarative routing config. What this change dictates is for routes to be placed in the components which render them (which makes sense), but how is something like that applied to a universal app where certain UI routes may exist on the client-side, but not the server-side (i.e. I want my blog posts to be rendered using SSR, but not the admin portal since I don’t need SEO for that).

To be continued

Everything detailed in the previous paragraphs is as far as I was able to explore in the world of Universal Javascript and server-side rendering. The link to the repository is below and I hope to publish future updates when I make progress.

Like what you read? Give Michael Glitzos a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.