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:
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
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 [email protected] [email protected]
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 [email protected] [email protected] [email protected]
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 [email protected] [email protected] [email protected] [email protected]
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 [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected]
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 [email protected]
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 [email protected]
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 [email protected] [email protected] [email protected] [email protected]
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:
debugger
s and console.log
s and transpiling them to ES5.1
styles.css
in a separate file and compressing and omitting all commentsstat.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 [email protected] [email protected] [email protected]
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.
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