Web Application implementation based on React.js, Complete Example

Written by amerllica | Published 2018/08/20
Tech Story Tags: javascript | programming | react | reactjs | nodejs

TLDRvia the TL;DR App

A perfect React application on the cutting edge of technology

Introduction

Every developer can search on the web and find something related to web application based on React and see some examples of implementations. some concepts like:

  • Server-Side Rendering
  • Using React Router
  • Using CSS Modules
  • Using PostCSS
  • Webpack configuration
  • Express
  • Configuration for separating Development and Production environment
  • Powerful tools for big scale application deployment
  • Application scaling
  • Progressive Web Application

They are too many and very complicated that sometimes, a crowd of numbers of these technologies causes not to implement them correctly.Sometimes we see websites of large companies like Pinterest, Instagram or Facebook, and this question arises to us How they act and how they use all techniques without any conflict with a large number of developers.

In this article, I wanna show you, straightforward vision or example to a good platform, complete and able to transform to a large scale. I explain it step by step.

In the end, you have a complete repository that you can build a good react application which contains trend techniques and technologies. Surely, your exportation will look like good examples in this field but this doesn’t mean you can not make it better than it is now.

TL;DR! : If you are bored to read all the article, and you want to see the results now, see and use this repository.

I wish, if you have ideas or vision to make it better, Fork it on Github and PR it. Andrew and I fully welcome this.

What we wanna build

If you seek for React App on the internet, you can find many implementations that they have SSR, React Helmet and many other things. but many of them were written based on Development targets, not Production mode.

In this article, I wanna implement an awesome Development area that you can do your jobs as fast as you can and also, you can Have a Production area with the efficient standards technologies.

Tools and Technologies in this article

  • React
  • React Router 4
  • React Helmet
  • CSS Modules
  • PostCSS
  • Webpack 3
  • Babel
  • Express
  • PM2

Basic requirement

In fact, you should install Node, I Prefer the long-term support version (LTS) if you don’t have it, so, download and install it from Node Official Website.

Now What?

The next step is making a folder the application that we wanna create it. With folding the project files you can regulate your application and make it scalable. The best practices for folding are so different, in this article I used mine that is like best of best, but you can use your folding style.

Now, I name my project folder react-example and then based on your OS, open your command prompt area or terminal. I assume you open it. then go the react-example folder by using your terminal and then input below command:

npm init

This command alongside running ask you some questions. answer them but do not concern about it. this question and answers are for making the package.json file and you can easily edit it.

Now input these commands in your terminal:

npm install --save react@16.3.2 react-dom@16.3.2

Actually, with the above command, you installed two important dependencies of this article. A folder is made beside the package.json file that name is node_modules which these dependencies and others are kept there. Don’t care about them now. Those numbers are the version of the packages for this article. Seriously use these versions because they are compatible with each other and maybe when you are reading this article the earlier versions released.

Now next step, type below command:

npm install --save-dev babel-loader@7.0.0 babel-core@6.24.1 webpack@3.11.0

These are dependencies for Development area, the --save-dev install them but separate them from --save dependencies. for clearness, you can open and see thepackage.json file.

Now, it is time to install babel, the question is What is it?

With a straightforward explanation, you’re going to code very very awesome based on cutting-edge technologies, But this doesn’t mean when you export your project and prepare it for deployment, all of the browsers are awesome like you. Maybe users use ancient browsers, So, you should take care of them.Babel allows you to be like a hero, but at the end, when Webpack will build your project, it exports codes that all kind of browsers understands them, both young and old of them, So, now paste below command in your terminal:

npm install --save-dev babel-preset-es2015@6.24.1 babel-preset-react@6.24.1 babel-preset-env@1.5.1 babel-preset-stage-0@6.24.1

These are babel's plug-ins and dependencies, don’t not attention to them now, but later, when you need something, definitely, you will seek and find these kinds of plug-ins. for using these plug-ins you must create a file with a .babelrc name and write inside it these codes:

{"presets": ["env","es2015","react","stage-0"]}

All lines of the above codes have their own meaning. for example es2015 means, let us use ES6 syntax and after building the project you will get just ES5.1 codes. or react cause to the project system understand JSX syntax and after build you just see createElement. I don’t explain much more, you can search about these presets and plug-ins or ask about them in the comment.

Webpack configuration

Until now, All files and configs are basic and we put all of them on the root of the project folder, but now we settle configs inside some folders to regulate project. This kind of folding cause the project will be regular and flexible to change and will be scalable.

Just like I said, we have two kind of environment, the Development and the Production, do for this target make a folder and name that webpack. inside of it make file and name it webpack.development.config.js and write its content as below:

const path = require('path');const ExtractTextPlugin = require('extract-text-webpack-plugin');

const distDir = path.join(__dirname, '../dist');const srcDir = path.join(__dirname, '../src');

module.exports = [{name: 'client',target: 'web',entry: `${srcDir}/client.jsx`,output: {path: path.join(__dirname, 'dist'),filename: 'client.js',publicPath: '/dist/',},resolve: {extensions: ['.js', '.jsx']},devtool: 'source-map',module: {rules: [{test: /\.(js|jsx)$/,exclude: /(node_modules[\\\/])/,use: [{loader: 'babel-loader',}]},{test: /\.pcss$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',use: [{loader: 'css-loader',options: {modules: true,importLoaders: 1,localIdentName: '[local]',sourceMap: true,}},{loader: 'postcss-loader',options: {config: {path: `${__dirname}/../postcss/postcss.config.js`,}}}]})},],},plugins: [new ExtractTextPlugin({filename: 'styles.css',allChunks: true})]},{name: 'server',target: 'node',entry: `${srcDir}/server.jsx`,output: {path: path.join(__dirname, 'dist'),filename: 'server.js',libraryTarget: 'commonjs2',publicPath: '/dist/',},resolve: {extensions: ['.js', '.jsx']},module: {rules: [{test: /\.(js|jsx)$/,exclude: /(node_modules[\\\/])/,use: [{loader: 'babel-loader',}]},{test: /\.pcss$/,use: [{loader: 'isomorphic-style-loader',},{loader: 'css-loader',options: {modules: true,importLoaders: 1,localIdentName: '[local]',sourceMap: false}},{loader: 'postcss-loader',options: {config: {path: `${__dirname}/../postcss/postcss.config.js`,}}}]}],},}];

As you see, in this file we distinguish client and server and set some configs for each of sides. somethings are similar and somethings are not. This article is too long now and for more explanation ask your question about configs in comments.

PostCSS configuration

Good, it is essential to be said that this Preprocessor is so different from its friends, like SCSS. You must also install its plug-ins in addition to the installation. With plug-ins, it will become stronger. And, If anywhere you feel or see some jobs exist that PostCSS cannot leverage it, you can search and find a plug-in to settle the problem. I chose some enough plug-ins, So let’s install them:

npm install --save-dev autoprefixer@8.3.0 css-loader@0.28.4 css-mqpacker@6.0.2 isomorphic-style-loader@4.0.0 postcss@6.0.21 postcss-apply@0.10.0 postcss-cssnext@3.1.0 postcss-custom-properties@7.0.0 postcss-extend@1.0.5 postcss-loader@2.1.4 postcss-nested@3.0.0 postcss-nested-ancestors@2.0.0 postcss-partial-import@4.1.0 postcss-scss@1.0.5 style-loader@0.21.0 extract-text-webpack-plugin@3.0.2

In the root of the project folder, make another folder and name it postcss, and make a file inside it as postcss.config.js and put below configuration in it. maybe you ask Why you don’t use SCSS or Less. If you search a little about its benefits, undoubtedly you believe this awesome preprocessor. One of its benefits is autoprefixer that vanish your need to mixins. Just with a little config, you can change the support level of browsers:

module.exports = {ident: 'postcss',syntax: 'postcss-scss',map: {'inline': true,},plugins: {'postcss-partial-import': {'prefix': '_','extension': '.pcss','glob': false,'path': ['./../src/styles']},'postcss-nested-ancestors': {},'postcss-apply': {},'postcss-custom-properties': {},'postcss-nested': {},'postcss-cssnext': {'features': {'nesting': false},'warnForDuplicates': false},'postcss-extend': {},'css-mqpacker': {'sort': true},'autoprefixer': {'browsers': ['last 15 versions']},}};

Now The App

It is so obvious that our application has several pages and should something exists that manage the route of these pages, and here I introduce my dear friend, the React Router, Pay attention please, I wanna use the 4th version, so install it with special below command:

npm install --save react-router-dom@4.2.2

And then make a folder in the root of project folder and name it src then make a file and name it as client.jsx and put below command in it:

import React from 'react';import {hydrate} from 'react-dom';import {BrowserRouter} from 'react-router-dom';import App from './app/App';

hydrate((<BrowserRouter><App/></BrowserRouter>), document.getElementById('root'));

Then make a another file alongside above file and name it as server.jsx and paste below codes inside it:

import React from 'react';import ReactDOMServer from 'react-dom/server';import {StaticRouter} from 'react-router-dom';import {Helmet} from "react-helmet";import Template from './app/template';import App from './app/App';

export default function serverRenderer({clientStats, serverStats}) {return (req, res, next) => {const context = {};const markup = ReactDOMServer.renderToString(<StaticRouter location={req.url} context={context}><App/></StaticRouter>);const helmet = Helmet.renderStatic();

res.status(200).send(Template({markup: markup,helmet: helmet,}));};};

And now make two folders alongside client.jsx and server.jsx files, and name them as app and styles. First is for our main application files and second for its CSS style files.

Application files

Inside of app folder make a file and name it as template.jsx and put the main HTML template in it that React wanna inject its markups.

export default ({ markup, helmet }) => {return `<!DOCTYPE html><html ${helmet.htmlAttributes.toString()}><head>${helmet.title.toString()}${helmet.meta.toString()}${helmet.link.toString()}</head><body ${helmet.bodyAttributes.toString()}><div id="root">${markup}</div><script src="/dist/client.js" async></script></body></html>`;};

Then make a App.jsx file and it is the main file of this project, The main file that contain every part of our React Application. I put some simple codes inside it, but you can folding and make larger application:

import React, {Component} from 'react';

export default class App extends Component {constructor(props) {super(props);}

render() {return (<div><h1>Hello World!</h1></div>);}}

A little ahead, I’ll return and add some more codes, but now it has enough code to be understood, After watching our simple Hello World! inside browser come back and make it more complete.

Just add a simple thing, React-Helmet, it’s awesome and has much good SEO efficient. In fact, this awesome component fill head tag dynamically and put many options for managing pages. For installing React-Helmet use below command inside your terminal:

npm install --save react-helmet@5.2.0

Just install it now. When we comeback to App.jsx for completion you will aware of its benefits.

Styles Files

Inside styles folder make a file and name it styles.pcss. And alongside it make a folder and name it partials, then inside partials folder make a partial file and name it as _partial.pcss and put below codes inside every file:

// styles.pcss@import "partials/partial";

.component {@extend %box;color: #2f95ff;}

.text {display: flex;@extend %box;}

.test {display: flex;}

.active {color: red;}

And

// partials/_partial.pcss%box {box-shadow: 0 0 10px 1px #ff6fc3;}

Development Server Configuration

The server of our Development area is express.js. In fact, we use some middleware that when we change something inside every file of the project, for example, PostCSS files, the build system will be aware and rebuild all files again and again automatically.

First should install dependencies packages:

npm i --save-dev express@4.15.3 webpack-dev-middleware@2.0.6 webpack-hot-middleware@2.22.1 webpack-hot-server-middleware@0.5.0

Then inside of root make a folder and name it express, and put Development configuration in it, for this please make a file and name it as development.js and write this file like below:

const express = require('express');const app = express();const webpack = require('webpack');const config = require('./../webpack/webpack.development.config.js');const compiler = webpack(config);const webpackDevMiddleware = require('webpack-dev-middleware');const webpackHotMiddleware = require('webpack-hot-middleware');const webpackHotServerMiddleware = require('webpack-hot-server-middleware');

app.use(webpackDevMiddleware(compiler, {serverSideRender: true,publicPath: "/dist/",}));app.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));app.use(webpackHotServerMiddleware(compiler));

const PORT = process.env.PORT || 3000;

app.listen(PORT, error => {if (error) {

return console.error(error);

} else {

console.log(`Development Express server running at http://localhost:${PORT}`);}});

Let’s See What We build

It’s time to test and see what we build until now, for this action it is needed to run below command inside your terminal:

node ./express/development.js

If you are working on Windows OS, maybe you see NODE_ENV error, don’t worry and visit this address. I wrote what you should do there, If it has no error you should see below log inside your terminal area:

Development Express server running at http://localhost:3000

If you see above log inside your terminal, you reached halfway of our road. Now open http://localhost:3000 in your browser, and enjoy your Masterpiece.

React Router Configuration

Everything is amazing till now, we bind many items alongside each other truly and All of them work as well as a Swiss watch. I insist, if you have any issue please leave a comment and I definitely will answer.

If you remember, we just installed React Router and didn’t import it to application, now time to return to App.jsx, now edit this file with the following code:

import React, {Component} from 'react';import Helmet from "react-helmet";import {Switch, Route} from 'react-router-dom';import {Link, NavLink} from 'react-router-dom';import styles from '../styles/styles.pcss';

class Menu extends Component {render() {return (<div><ul><li><NavLink exact to={'/'} activeClassName={styles.active}>Homepage</NavLink></li><li><NavLink activeClassName={styles.active} to={'/about'}>About</NavLink></li><li><NavLink activeClassName={styles.active} to={'/contact'}>Contact</NavLink></li></ul></div>);}}

class Homepage extends Component {

render() {return (<div className={styles.component}><Helmet title="Welcome to our Homepage"/><Menu/><h1>Homepage</h1></div>);}}

class About extends Component {render() {return (<div><Helmet title="About us"/><Menu/><h1>About</h1></div>);}}

class Contact extends Component {render() {return (<div><Helmet title="Contact us"/><Menu/><h1>Contact</h1></div>);}}

export default class App extends Component {

render() {return (<div><HelmethtmlAttributes={{lang: "en", amp: undefined}} // amp takes no valuetitleTemplate="%s | React App"titleAttributes={{itemprop: "name", lang: "en"}}meta={[{name: "description", content: "Server side rendering example"},{name: "viewport", content: "width=device-width, initial-scale=1"},]}link={[{rel: "stylesheet", href: "/dist/styles.css"}]}/><Switch><Route exact path='/' component={Homepage}/><Route path='/about' component={About}/><Route path='/contact' component={Contact}/></Switch></div>);}}

Sounds good, let me explain this new content, we have three components, Homepage, About and Contact That they play the role of our three pages.

After these three pages, we make a Menu component for showing menu in each component or actually each page.

Here is a point, we are not supposed to use class names like the past. We are going to use CSS-Modules, so, we should add the styles root file like a JavaScript object:

import styles from '../styles/styles.pcss';

And use it in a class name like a JavaScript object:

<div className={styles.container}><div className={styles['container-top']}example</div></div>

I think it is so obvious, in the PostCSS files leave any class names or name spacing methodologies, like BEM, and inside JSX use that name like a child of styles object instead of direct name.This kind of using cause when you prepare the deployment version hence Production version, the class names will be transformed to hash names and you can make them small to 5 characters. Definitely, your built CSS file will be very small and compact.

The other thing that I like it, is React Helmet, that you can see in the above codes obviously. In each component React Helmet exists separately, In the root component, it exists too for general head settings. These settings are very straightforward so I refuse to explain it here.

Now, let’s see what we build again, so run Development command again:

node ./express/development.js

It’s like a miracle, yeah, you build a skeleton of a web application based on React.js that has many awesome technologies. when you click on each menu item the related page renders from the server and it is dynamic on your browser, but is it done?

Both yes and no, YES: Because we built an awesome Development area, NO: Because we don’t know how to prepare it for Production area and deployment.

Preparing for deployment on Production environment

So far, we’ve done everything for Development, but now we settle different configuration for Production so we should consider these three goals:

  1. Bundling and compressing all files in our application and vanishing debuggers and console.logs and transpiling them to ES5.1
  2. Extracting styles.css in a separate file and compressing and omitting all comments
  3. Building stat.json file that in fact, it is stuff pieces of webpack on the server actions, which express needs them.

Going to Production configuration with following commands:

npm install --save-dev clean-webpack-plugin@0.1.19 stats-webpack-plugin@0.6.0 optimize-css-assets-webpack-plugin@3.2.0

Now create another file in webpack folder that its name is webpack.production.config.js. its content is:

const path = require('path');const webpack = require('webpack');const ExtractTextPlugin = require('extract-text-webpack-plugin');const StatsPlugin = require('stats-webpack-plugin');const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');const CleanWebpackPlugin = require('clean-webpack-plugin');

const distDir = path.join(__dirname, '../dist');const srcDir = path.join(__dirname, '../src');

module.exports = [{name: 'client',target: 'web',entry: `${srcDir}/client.jsx`,output: {path: distDir,filename: 'client.js',publicPath: distDir,},resolve: {extensions: ['.js', '.jsx']},module: {rules: [{test: /\.(js|jsx)$/,exclude: /(node_modules[\\\/])/,use: [{loader: 'babel-loader',}]},{test: /\.pcss$/,use: ExtractTextPlugin.extract({fallback: 'style-loader',use: [{loader: 'css-loader',options: {modules: true,importLoaders: 1,localIdentName: '[hash:base64:10]',sourceMap: false,}},{loader: 'postcss-loader',options: {config: {path: `${__dirname}/../postcss/postcss.config.js`,}}}]})}],},plugins: [new ExtractTextPlugin({filename: 'styles.css',allChunks: true}),new webpack.DefinePlugin({'process.env': {NODE_ENV: '"production"'}}),new CleanWebpackPlugin(distDir),new webpack.optimize.UglifyJsPlugin({compress: {warnings: false,screw_ie8: true,drop_console: true,drop_debugger: true}}),new webpack.optimize.OccurrenceOrderPlugin(),]},{name: 'server',target: 'node',entry: `${srcDir}/server.jsx`,output: {path: distDir,filename: 'server.js',libraryTarget: 'commonjs2',publicPath: distDir,},resolve: {extensions: ['.js', '.jsx']},module: {rules: [{test: /\.(js|jsx)$/,exclude: /(node_modules[\\\/])/,use: [{loader: 'babel-loader',}]},{test: /\.pcss$/,use: [{loader: 'isomorphic-style-loader',},{loader: 'css-loader',options: {modules: true,importLoaders: 1,localIdentName: '[hash:base64:10]',sourceMap: false}},{loader: 'postcss-loader',options: {config: {path: `${__dirname}/../postcss/postcss.config.js`,}}}]}],},plugins: [new OptimizeCssAssetsPlugin({cssProcessorOptions: {discardComments: {removeAll: true}}}),new StatsPlugin('stats.json', {chunkModules: true,modules: true,chunks: true,exclude: [/node_modules[\\\/]react/],}),]}];

And create another file in express folder and name it production.js and fill it by using below codes:

const express = require('express');const path = require('path');const app = express();const ClientStatsPath = path.join(__dirname, './../dist/stats.json');const ServerRendererPath = path.join(__dirname, './../dist/server.js');const ServerRenderer = require(ServerRendererPath).default;const Stats = require(ClientStatsPath);

app.use('/dist', express.static(path.join(__dirname, '../dist')));app.use(ServerRenderer(Stats));

const PORT = process.env.PORT || 3000;

app.listen(PORT, error => {if (error) {

return console.error(error);

} else {

console.log(`Production Express server running at http://localhost:${PORT}`);}});

So now, with below commands, we can build some files that they are needed for the Production environment:

NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors

You can see some files are generated inside a new folder that name is dist. you can see the semi-production environment that all visitors will see by using the following command:

NODE_ENV=production node ./express/production.js

If on your Google Chrome browser you had installed the React Developer Tools and Wappalyzer extensions, you can see the React logo, especially you can see the React Developer Tools turns to blue color and it is not red color yet_._

Blue means it is on production build and red means it is under construction of development build.

Congratulations! this article is so simple but a little complex, when you are here, it means you are perfect. It takes some time to find all the things out. But at last, you will make your custom project with many add on other things like eslint, Redux, Redux-Saga or testing like Jest and many other things.

Running on Real Server

When all the above stuff is prepared, you should tell your DevOps specialist that install pm2 on the Production server, because a large scale React application with a crowd of visitors never run with above command. the pm2 should install with this command:

npm install pm2 -g

And at last for running should use this command:

NODE_ENV=production pm2 start ./express/production.js

You can use pm2 on your own PC for a test, it is no different between pm2 and node commands in your browser but the DevOps specialist knows about their difference like load balance, managing caching and etc. If you like to know more about pm2 you can read its Docs.

Conclusion

For easy development, I put some commands in the scripts section of the package.json file. you can access them in the GitHub Repository or see below codes:

"scripts": {"test": "echo \"Error: no test specified\" && exit 1","dev": "NODE_ENV=development node ./express/development.js","build": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors","prod": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors && node ./express/production.js","pm2": "NODE_ENV=production pm2 start ./express/production.js"}

And the last point, it is right that I used npm in all of this article, for example you can use npm run dev for running development, but the npm is so slow, when each changing is happen in files, system will build your new development files about 27 to 30 seconds! later, and it is annoying for development. Then you can see your changes with browser Hard Reload. It’s so boring.For this issue, I suggest you use yarn. You can start to develop with yarn dev command, it is unbelievable, it longs about 500 milliseconds to build new development files.

Hope this article helps you to build your **React.js** application


Published by HackerNoon on 2018/08/20