Book Image

Isomorphic JavaScript Web Development

By : Tomas Alabes, Konstantin Tarkus
Book Image

Isomorphic JavaScript Web Development

By: Tomas Alabes, Konstantin Tarkus

Overview of this book

<p>The latest trend in web development, Isomorphic JavaScript, allows developers to overcome some of the shortcomings of single-page applications by running the same code on the server as well as on the client. Leading this trend is React, which, when coupled with Node, allows developers to build JavaScript apps that are much faster and more SEO-friendly than single-page applications.</p> <p>This book begins by showing you how to develop frontend components in React. It will then show you how to bind these components to back-end web services that leverage the power of Node. You'll see how web services can be used with React code to offload and maintain the application logic. By the end of this book, you will be able to save a significant amount of development time by learning to combine React and Node to code fast, scalable apps in pure JavaScript.</p>
Table of Contents (16 chapters)
Title Page
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Rendering a React app on both client and server


Look at the following two code snippets showing how to render the same top-level React component on the client (in a browser) and on the server (in Node.js app):

In order to render the App component on the client, you write:

import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.hydrate(<App />, document.getElementById('app'));

In order to render the same component on the server (in Node.js app), you write:

import ReactDOM from 'react-dom/server';
import App from './components/App';
const html = ReactDOM.renderToString(<App />);

Both methods will try to build an in-memory representation of the UI tree (aka virtual DOM) of the App component. The first one will compare that virtual DOM with the actual DOM inside the <div id="app"></div> HTML element and will modify the actual DOM to make it match the virtual DOM exactly. The second method will just convert the in-memory representation of the UI tree into HTML, which then can be sent to a client.

Now, let's see how a complete example for the client-side and server-side application code looks like. Go ahead and create client.js file with the following content:

import 'babel-core/register'; 
import React from 'react'; 
import ReactDOM from 'react-dom'; 
import App from './components/App'; 
 
function run() { 
  ReactDOM.hydrate(<App />, document.getElementById('app')); 
} 
 
const loadedStates = ['complete', 'loaded', 'interactive']; 
 
if (loadedStates.includes(document.readyState) && document.body) { 
  run(); 
} else { 
  window.addEventListener('DOMContentLoaded', run, false); 
}

This ensures that the React application is only mounted when the HTML page was fully loaded into the browsers.

For the server-side app, let's create server.js file with the following content:

import express from 'express'; 
import React from 'react'; 
import ReactDOM from 'react-dom/server'; 
import App from './components/App'; 
 
const server = express(); 
const port = process.env.PORT || 3000; 
server.get('*', (req, res) => { 
  const title = 'Sample Application'; 
  const app = ReactDOM.renderToString(<App />); 
  res.send(`<!doctype html> 
<html> 
<head> 
<title>${title}</title> 
<src script="client.js"></script> 
</head> 
<body> 
<div id="app">${app}</div> 
</body> 
</html>`); 
}); 
 
server.listen(port, () => { 
  console.log(`App is listening at http://localhost:${port}/`); 
}); 

It renders the App component to an HTML string, wraps it into a valid HTML5 document with <head> and <body> sections, and sends it to a client on all HTTP requests to the server.

We can go further and replace that ES7 string literal above with a React-based template in order not to worry about escaping HTML fragments. In order to do so, create components/Html.js file with the following content:

import React from 'react'; 
 
const Html = (props) => 
<html> 
<head> 
<meta charSet="utf-8" /> 
<meta httpEquiv="x-ua-compatible" content="ie=edge" /> 
<title>{props.title || ''}</title> 
<meta name="description" 
            content={props.description || ''} /> 
<meta name="viewport" 
            content="width=device-width, initial-scale=1" /> 
<script src="client.js" /> 
</head> 
<body> 
<div id="app" 
           dangerouslySetInnerHTML={{__html: props.children}} /> 
</body> 
</html>; 
 
export default Html; 

Since this component doesn't use state, we don't need to extend it from React.Component, but instead, we can use a regular function which accepts a collection of properties as an argument and returns a React component. Make sure that all the tags in the mark-up used in your React components are properly closed and you use valid JSX attributes. For example, instead of <meta charset="utf-8">, you should write <meta charSet="utf-8" /> and so on.

Note

If you're new to JSX syntax, visit the following two pages to get more information about it:https://facebook.github.io/react/docs/jsx-in-depthhttps://facebook.github.io/react/docs/jsx-gotchas.

Another addition which we can make to the server.js file is to add Express.js static middleware to make it serve static files, such as robots.txt from the /public folder. Now, the final server.js file should look like this:

import path from 'path'; 
import express from 'express'; 
import React from 'react'; 
import ReactDOM from 'react-dom/server'; 
import Html from './components/Html'; 
import App from './components/App'; 
 
const server = express(); 
const port = process.env.PORT || 3000; 
 
server.use(express.static(path.join(__dirname, 'public'))); 
 
server.get('*', (req, res) => { 
  const body = ReactDOM.renderToString(<App />); 
  const html = ReactDOM.renderToStaticMarkup(<Html 
    title="My App" 
    description="Isomorphic web application sample" 
    body={body} />); 
  res.send('<!doctype html>\n' + html); 
}); 
 
server.listen(port, () => console.log( 
  `Node.js server is listening at http://localhost:${port}/`
));