How to do it...

Let's now go through the steps of rendering:

  1. First, we need to add our npm scripts to our package.json file:
    "scripts": {
"clean": "rm -rf dist/ && rm -rf public/app",
"start": "npm run clean & NODE_ENV=development
BABEL_ENV=development
nodemon src/server --watch src/server --watch src/shared --
exec
babel-node --presets es2015",
"start-analyzer": "npm run clean && NODE_ENV=development
BABEL_ENV=development ANALYZER=true babel-node src/server"
}
File: package.json
  1. Now we have to change our webpack.config.js file. Because we are going to implement SSR, we need to separate our Webpack configuration into a client configuration and server configuration, returning them as an array. The file should look like this:
  // Webpack Configuration (Client & Server)
import clientConfig from './webpack/webpack.config.client';
import serverConfig from './webpack/webpack.config.server';

export default [
clientConfig,
serverConfig
];
File: webpack.config.js

  1. Now we need to create a file for our client configuration inside our webpack folder. We need to call it webpack.config.client.js:
  // Dependencies
import webpackMerge from 'webpack-merge';

// Webpack Configuration
import commonConfig from './webpack.config.common';
import {
context,
devtool,
entry,
name,
output,
optimization,
plugins,
target
} from './configuration';

// Type of Configuration
const type = 'client';

export default webpackMerge(commonConfig(type), {
context: context(type),
devtool,
entry: entry(type),
name: name(type),
output: output(type),
optimization,
plugins: plugins(type),
target: target(type)
});
File: webpack/webpack.config.client.js
  1. Now the server config should be like this:
  // Dependencies
import webpackMerge from 'webpack-merge';

// Webpack Configuration
import commonConfig from './webpack.config.common';

// Configuration
import {
context,
entry,
externals,
name,
output,
plugins,
target
} from './configuration';

// Type of Configuration
const type = 'server';

export default webpackMerge(commonConfig(type), {
context: context(type),
entry: entry(type),
externals: externals(type),
name: name(type),
output: output(type),
plugins: plugins(type),
target: target(type)
});
File: webpack/webpack.config.server.js
  1. As you can see, in both files we are importing a common configuration file that contains a configuration that needs to be added to both the client and the server:
  // Configuration
import { module, resolve, mode } from './configuration';
export default type => ({
module: module(type),
resolve,
mode
});
File: webpack/webpack.config.common.js
  1. We need to add new configuration files for Webpack nodes and also modify some of the files we already have. The first one we need to create is context.js. In this file (and some others) we are going to export a function with a type parameter, which can be client or server, and depending on that value we will return different configurations:
  // Dependencies
import path from 'path';
export default type => type === 'server'
? path.resolve(__dirname, '../../src/server')
: path.resolve(__dirname, '../../src/client');
File: webpack/configuration/context.js

  1. The entry file is where we will add all the files that are going to be added to the bundle. Our entry file now should be like this:
  // Environment
const isDevelopment = process.env.NODE_ENV !== 'production';

export default type => {
if (type === 'server') {
return './render/serverRender.js';
}

const entry = [];

if (isDevelopment) {
entry.push(
'webpack-hot-middleware/client',
'react-hot-loader/patch'
);
}

entry.push('./index.jsx');

return entry;
};
File: webpack/configuration/entry.js
  1. We need to create a file called externals.js, which contains the modules we won't bundle (unless they are on the whitelist):
  // Dependencies
import nodeExternals from 'webpack-node-externals';

export default () => [
nodeExternals({
whitelist: [/^redux/(store|modules)/]
})
];
File: webpack/configuration/externals.js

  1. Also, we need to modify our module.js file to return our rules based on the environment or the configuration type:
  // Dependencies
import ExtractTextPlugin from 'extract-text-webpack-plugin';

// Environment
const isDevelopment = process.env.NODE_ENV !== 'production';

export default type => {
const rules = [
{
test: /.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/
}
];

if (!isDevelopment || type === 'server') {
rules.push({
test: /.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader?minimize=true&modules=true&localIdentName=
[name]__[local]_[hash:base64]',
'sass-loader'
]
})
});
} else {
rules.push({
test: /.scss$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]_[hash:base64]',
sourceMap: true,
minimize: true
}
},
{
loader: 'sass-loader'
}
]
});
}

return {
rules
};
};
File: webpack/configuration/module.js
  1. Now we need to create a node for the name:
  export default type => type;
File: webpack/configuration/name.js
  1. For the output configuration, we need to return an object depending on the type of configuration (client or server):
  // Dependencies
import path from 'path';

export default type => {
if (type === 'server') {
return {
filename: 'server.js',
path: path.resolve(__dirname, '../../dist'),
libraryTarget: 'commonjs2'
};
}

return {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../../public/app'),
publicPath: '/'
};
};
File: webpack/configuration/output.js

  1. In our plugins.js file, we are validating whether the user has sent the ANALYZER variable to display the BundleAnalyzerPlugin just in that case and not every time we run our application in development mode:
  // Dependencies
import CompressionPlugin from 'compression-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import webpack from 'webpack';
import WebpackNotifierPlugin from 'webpack-notifier';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

// Environment
const isDevelopment = process.env.NODE_ENV !== 'production';

// Analyzer
const isAnalyzer = process.env.ANALYZER === 'true';

export default type => {
const plugins = [
new ExtractTextPlugin({
filename: '../../public/css/style.css'
})
];

if (isAnalyzer) {
plugins.push(
new BundleAnalyzerPlugin()
);
}

if (isDevelopment) {
plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new WebpackNotifierPlugin({
title: 'CodeJobs'
})
);
} else {
plugins.push(
new CompressionPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /.js$|.css$|.html$/,
threshold: 10240,
minRatio: 0.8
})
);
}

return plugins;
};
File: webpack/configuration/plugins.js
  1. We need to specify our modules in our resolve file; the file should be like this:
  // Dependencies
import path from 'path';

export default {
extensions: ['.js', '.jsx'],
modules: [
'node_modules',
path.resolve(__dirname, '../../src/client'),
path.resolve(__dirname, '../../src/server')
]
};
File: webpack/configuration/resolve.js
  1. The last configuration we need to create is the target.js file:
  export default type => type === 'server' ? 'node' : 'web';
File: webpack/configuration/target.js
  1. After we have configured our Webpack, we need to modify our App.jsx file, in which we need to create our routes for the client using the <BrowserRouter> component and <StaticRouter> for the server:
  // Dependencies
import React from 'react';
import {
BrowserRouter,
StaticRouter,
Switch,
Route
} from 'react-router-dom';

// Components
import About from '@components/About';
import Home from '@components/Home';

export default ({ server, location, context = {} }) => {
const routes = (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
</Switch>
);

// Client Router
let router = (
<BrowserRouter>
{routes}
</BrowserRouter>
);

// Server Router
if (server) {
router = (
<StaticRouter location={location} context={context}>
{routes}
</StaticRouter>
);
}

return router;
};
File: src/client/App.jsx
  1. Now we need to modify our server file (index.js) to use our clientRender and serverRender middleware:
  // Dependencies
import express from 'express';
import path from 'path';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import webpackHotServerMiddleware from 'webpack-hot-server-middleware';
import webpack from 'webpack';

// Utils
import { isMobile, isBot } from '@utils/device';

// Client Render
import clientRender from './render/clientRender';

// Webpack Configuration
import webpackConfig from '@webpack';

// Environment
const isProduction = process.env.NODE_ENV === 'production';

// Express Application
const app = express();

// Webpack Compiler
const compiler = webpack(webpackConfig);

// Public directory
app.use(express.static(path.join(__dirname, '../../public')));

// Device Detection
app.use((req, res, next) => {
req.isMobile = isMobile(req.headers['user-agent']);
// We detect if a search bot is accessing...
req.isBot = isBot(req.headers['user-agent']);

next();
});

// Webpack Middleware
if (!isProduction) {
// Hot Module Replacement
app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(
compiler.compilers.find(compiler => compiler.name === 'client'))
);
} else {
// GZip Compression just for Production
app.get('*.js', (req, res, next) => {
req.url = `${req.url}.gz`;
res.set('Content-Encoding', 'gzip');
next();
});
}

// Client Side Rendering
app.use(clientRender());

if (isProduction) {
try {
// eslint-disable-next-line
const serverRender = require('../../dist/server.js').default;

app.use(serverRender());
} catch (e) {
throw e;
}
}

// For Server Side Rendering on Development Mode
app.use(webpackHotServerMiddleware(compiler));

// Disabling x-powered-by
app.disable('x-powered-by');

// Listen Port...
app.listen(3000);
File: src/server/index.js
  1. We need to modify our clientRender.js file. If we detect a search bot with the isBot function, we will return the next() middleware. Otherwise, we render the HTML and we execute the app with CSR: 
  // HTML
import html from './html';

// Initial State
import initialState from './initialState';

export default function clientRender() {
return (req, res, next) => {
if (req.isBot) {
return next();
}

res.send(html({
title: 'Codejobs',
initialState: initialState(req)
}));
};
}
File: src/server/render/clientRender.js

  1. Now let's create our serverRender.js file. Here, we need to render our App component using the renderToString method from react-dom/server library:
  // Dependencies
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';

// Redux Store
import configureStore from '@configureStore';

// Components
import App from '../../client/App';

import html from './html';

// Initial State
import initialState from './initialState';

export default function serverRender() {
return (req, res, next) => {
// Configuring Redux Store
const store = configureStore(initialState(req));

const markup = renderToString(
<Provider store={store}>
<App
server
location={req.url}
/>
</Provider>
);

res.send(html({
title: 'Codejobs',
markup,
initialState: initialState(req)
}));
};
}
File: src/server/render/serverRender.js
..................Content has been hidden....................

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