A basic example

We will now create a very simple server-side application to look at the steps that are needed to build a basic Universal setup.

It is going to be a minimal and simple setup on purpose because the goal here is to show how SSR works rather than providing a comprehensive solution or a boilerplate, even though you could use the example application as a starting point for a real-world application.

This section assumes that all the concepts regarding JavaScript build tools, such as webpack and its loaders, are clear, and it requires a little bit of knowledge of Node.js. As a JavaScript developer, it should be easy for you to follow this section, even if you have never seen a Node.js application before.

The application will consist of two parts:

  • The server side, where we will use Express to create a basic web server and serve an HTML page with the server-side rendered React application

  • The client side, where we will render the application, as usual, using react-dom

Both sides of the application will be transpiled with Babel and bundled with webpack before being run, which will let us use the full power of ES6 and the modules both on Node.js and on the browser.

Let's start by moving into an empty folder and running the following to create a new package:

npm init

When package.json has been created, it is time to install the dependencies. We can start with webpack:

npm install webpack

After it is done, it is time to install the Babel loader and the presets that we need to write an ES6 application using React and JSX:

npm install @babel/core @babel/preset-env @babel/preset-react babel-loader

We also have to install a dependency, which we will need to create the server bundle. The webpack lets us define a set of externals, which are dependencies that we do not want to add to the bundle. When creating a build for the server, in fact, we do not want to add to the bundle of all the node packages that we use; we just want to bundle our server code. There's a package that helps with that, and we can simply apply it to the external entry in our webpack configuration to exclude all the modules:

npm install webpack-node-externals

Great, it is now time to create an entry in the npm scripts section of package.json so that we can easily run the build command from the terminal:

  "scripts": {
"build": "webpack"
}

We now have to create the configuration file, called webpack.config.js, to tell webpack how we want our files to be bundled.

Let's start importing the library we will use to set our node externals. We will also define the configuration for the Babel loader, which we will use for both the client and the server:

  const nodeExternals = require('webpack-node-externals');

const rules = [
{
test: /.(js?|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
}
];

In Chapter 7, Make Your Components Look Beautiful, we looked at how we had to export a configuration object from the configuration file. There is one cool feature in webpack that lets us export an array of configurations as well so that we can define both client and server configurations in the same place and use both in one go.

The client configuration should be very familiar:

const client = {
entry: './src/client.js',
output: {
path: './dist/public',
filename: 'bundle.js'
},
module: {
rules
}
};

We are telling webpack that the source code of the client application is inside the src folder, and we want the output bundle to be generated in the dist folder.

We also set the module loaders using the previous object we created with babel-loader. It was done as simple as possible.

The server configuration is slightly different, but it should be very easy for you to follow and understand:

  const server = {
entry: './src/server.js',
output: {
path: './dist',
filename: 'server.js'
},
module: {
rules
},
    target: 'node',
externals: [nodeExternals()]
};

As you can see, entry, output, and module are the same, except for the file names.

The new parameters are the target, where we specify the node to tell webpack to ignore all the built-in system packages of Node.js, such as fs and externals, where we use the library we imported earlier to tell webpack to ignore the dependencies.

Last, but not least, we have to export the configurations as an array:

  module.exports = [client, server];

The configuration is done. We are now ready to write some code, and we will start from the React application, which we are more familiar with.

Let's create an src folder and an app.js file inside it.

The app.js file should have the following content:

  import React from 'react';

const App = () => <div>Hello React</div>;

export default App;

Nothing complex here: we import React, we create an App component, which renders the Hello React message, and we export it.

Let's now create client.js, which is responsible for rendering the App component inside the DOM:

  import React from 'react';
import { render } from 'react-dom';
import App from './app';

render(<App />, document.getElementById('app'));

Again, this should sound familiar, since we import React, ReactDOM, and the App component we created earlier, and we use ReactDOM to render it in a DOM element with the appID.

Let's now move to the server.

The first thing to do is create a template.js file, which exports a function that we will use to return the markup of the page that our server will give back to the browser:

  export default body => `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${body}</div>
<script src="/bundle.js"></script>
</body>
</html>`;

It should be pretty straightforward, the function accepts body, which we will later see contains the React app, and it returns the skeleton of the page.

It is worth noting that we load the bundle on the client side even if the app is rendered server side. SSR is only half of the job that React does to render our application. We still want our application to be a client-side application with all the features we can use in the browser, such as event handlers, for example.

After this, you need to install express, react, and react-dom:

npm install express react react-dom

Now it is time to create server.js, which has more dependencies and is worth exploring in detail:

  import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import path from 'path';
import App from './app';
import template from './template';

The first thing that we import is express, the library that allows us to create a web server with some routes easily, and which is also able to serve static files.

Secondly, we import React and ReactDOM to render App, which we import as well. Notice the /server path in the import statement of ReactDOM. The last thing we import is the template we defined earlier.

Now we create an Express application:

  const app = express();

We tell the application where our static assets are stored:

  app.use(express.static(path.resolve(__dirname, './dist/public')));

As you may have noticed, the path is the same that we used in the client configuration of webpack as the output destination of the client bundle.

Then, here comes the logic of SSR with React:

  app.get('/', (req, res) => {
const body = renderToString(<App />);
const html = template(body);
res.send(html);
});

We are telling Express that we want to listen to the route, /, and when it gets hit by a client, we render App to a string using the ReactDOM library. Here come the magic and the simplicity of the server-side rendering of React.

What renderToString does is it returns a string representation of the DOM elements generated by our App component; the same tree that it would render in the DOM if we were using the ReactDOM render method.

The value of the body variable is something like the following:

<div data-reactroot="" data-reactid="1" data-react-checksum="982061917">Hello React</div>

As you can see, it represents what we defined in the render method of our App, except for a couple of data attributes that React uses on the client to attach the client-side application to the server-side rendered string.

Now that we have the SSR representation of our app, we can use the template function to apply it to the HTML template and send it back to the browser within the Express response.

Last, but not least, we have to start the Express application:

  app.listen(3000, () => {
console.log('Listening on port 3000');
});

We are now ready to go; there are only a few operations left.

The first one is defining the start script of npm and setting it to run the node server:

  "scripts": {
"build": "webpack",
"start": "node ./dist/server"
}

The scripts are ready, so we can first build the application with the following:

  npm run build 

When the bundles are created, we can run the following command:

npm start

Point the browser to http://localhost:3000 and see the result.

There are two important things to note here. First, we use the View Page Source feature of the browser, we can see the source code of the application being rendered and returned from the server, which we would not see if SSR was not enabled.

Second, if we open DevTools and we have the React extension installed, we can see that the App component has been booted on the client as well.

The following screenshot shows the source of the page:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.171.121