React | Simple implementing SSR(Server-Side Rendering) in React
Hello! My name is Clark, in this post we are gonna implementing Server-Side Rendering in React, it’s very easy! Let’s start!
Server-Side Rendering vs Client-Side Rendering
What different between SSR and CSR? first, the CSR have a few step on page of web loading:
-
Load HTML file, It usually likes this:
<html> <body> <div id="root"></div> <script src="./bundle.js"></script> </body> </html>
- Download JavaScript bundle.js.
- Last, execute bundle.js and render page view by route!
On the other hand, SSR have these step:
- Send request to NodeJS server when type the URL.
- Server would be generate the view of page by request URL path, and response to client side.
-
Client accept the response from server, It likes:
<html> <body> <div id="root"> <!-- Page information --> </div> <script src="./bundle.js"></script> </body> </html>
- Download JavaScript bundle.js.
- Last, execute bundle.js and render page view by route!
Now you can according to above loading step of about CSR and SSR find out a key thing! did you find that?
Yes! The different just is action before Download JavaScript bundle.js. this step.
So in the face, we just need prepare Server for the first load, it’s very easy!
And before the start, I have ready a basic CSR project of react, you can clone it from my Github, when you cloned you can execute it, and find it first loading hasn’t any content on page
Right now, following step to implement SSR to this project!
Create server by NodeJS
First of all, for the SSR we must use express server library of NodeJS, why I chooses NodeJS? because just only NodeJS can implement SSR, They are all JavaScript.
Install express
Install express in origin CSR project.
npm install express --save
Then add the server.js in src folder, and write follow code:
import express from 'express';
// (1)
const app = express();
const port = process.env.PORT || 3001;
// (2)
app.use(express.static('dist'));
// (3)
app.get('*', (req, res) => {
//(4)
const content = '<div>Response Content</div>';
res.send(content);
});
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
- Create express object, and Setting folder position of static file.
- Setting folder position of static file.
- Route entrance, if setting ’*’ then any route would be execute callback function, and callback have two parameter, the first is information of request(example path), the second is method of response.
- Setting content of page, and use
res.send
response to client-side.
Ok! we have gone finish the server part, but this response of server in not we expect, we want the content is according to router render correspond component.
So we need a helper, it can help render first content same as CSR.
Rendering content for first response
Please create new folder in src folder, it name is helper, and create a file in this, new file name is renderer.js, last write follow code into it:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { renderRoutes } from 'react-router-config';
import Routes from '../Routes';
export default (req) => {
// (1)
const content = renderToString(
<StaticRouter location={req.path}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>,
);
// (2)
return `
<html>
<body>
<div id="root">${content}</div>
<script src="./bundle.js"></script>
</body>
</html>
`;
};
Let’s look What did it do:
-
The content is according to router rendering correspond content, it have two points:
The first is
StaticRouter
, it is kind of router like asHashRouter
andBrowserRouter
, we can use it, when route never change(Document of StaticRouter).The second is
Routes
from ‘../Routes’, because same of all content of page, whether rendered from client or server, so we can reuse it. -
Because the first loading is from server-side in SSR, so we have to prepare basic HTML for after ‘.bundle.js’, then when ‘.bundle.js’ loaded just can continue support actions for operating of user.
You can image it is for origin ‘./dist/index.html’ instead.
Now we have finished ‘./src/helper/renderer.js’, next back to ‘./src/server.js’ to use it:
import renderer from './helpers/renderer';
...
app.get('*', (req, res) => {
// Call renderer get content by `req` parameter
const content = renderer(req);
// Would content send to client
res.send(content);
});
...
Setting Webpack to build server
We have already finished all about code! we need setting Webpack now, let it can build server, first open ‘./webpack.config.js’, it config is for build client code(‘./src/client.js’), we have to create new config of Webpack for build server.
So create new file in root, and name is ‘webpack.server.js’, and copy content of ‘./webpack.config.js’ to new file modify something:
./webpack.server.js
const path = require('path');
// (1)
const webpackNodeExternals = require('webpack-node-externals');
module.exports = {
// (2)
target: 'node',
// (3)
entry: ['@babel/polyfill', './src/server.js'],
// (4)
externals: [webpackNodeExternals()],
output: {
filename: 'bundle.js',
// (5)
path: path.resolve(__dirname, './build'),
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
},
},
},
],
},
};
I modified something:
-
Install webpack-node-externals from npm:
npm i webpack-node-externals
When build on server, not need contain node_modules, so we can use webpack-node-externals ignore this on building.
- Server running environment is Node, It have some different setting with browser when build, so have to defined this.
- Modify entry file from ‘./src/client.js’ to ‘./src/server.js’.
- Use the first step installed webpack-node-externals to externals.
- Defined new folder(different with client code) to put build file of server code.
Then we could rename ‘./webpack.config.js’ to ‘./webpack.client.js’.
Build and running
Finally, add and modify content on script of ‘package.json’:
package.json
"scripts": {
"build-client": "webpack --config webpack.client.js",
"build-server": "webpack --config webpack.server.js",
"build": "npm run build-client && npm run build-server",
"server": "node build/bundle.js"
},
Running script of build:
Running server:
npm run server
Then open browser, and type localhost:3001
to url, and look at the response on first loading:
The first response have complete content, next would bundle.js(client code) download, after download, then execute process all by client side, server just only be responsible for first render!
Above about SSR code would find in my GitHub,
Conclusion
The base SSR is not difficult, just prepare little server code and setting for server render, in the future, I will write SSR about react-redux.
Thank for reading my post, if you have any question and think, please let me know, in comments below :)