├── .babelrc
├── .env
├── .env.development
├── .env.production
├── .env.test
├── .eslintrc.json
├── .gitignore
├── FakeApi
├── fakeApi.js
└── help.txt
├── README.md
├── package.json
├── provider
├── server
│ ├── development.js
│ └── production.js
├── setup
│ ├── constant.js
│ ├── envLoader.js
│ ├── fileVersion.js
│ └── rateLimit.js
└── webpack
│ ├── development.js
│ └── production.js
├── public
├── app-icon.png
├── asset
│ └── img
│ │ ├── error-404.png
│ │ ├── loading.gif
│ │ ├── rssr-logo-block.png
│ │ └── rssr-logo.png
├── manifest.json
└── sub-scripts.js
└── src
├── App
├── App.js
├── Error404
│ └── Error404.js
├── Home
│ ├── Home.js
│ └── home.scss
├── Post
│ └── Post.js
└── Sign
│ └── Sign.js
├── Component
├── Auth
│ ├── InvalidUser.js
│ ├── LoadingUser.js
│ ├── ResetPassword.js
│ ├── SignIn
│ │ ├── ForgetPasswordForm.js
│ │ ├── SignIn.js
│ │ └── SignInForm.js
│ ├── SignUp.js
│ ├── UpdatedUser.js
│ ├── ValidUser.js
│ └── __action
│ │ ├── authentication.js
│ │ ├── firstSetup.js
│ │ ├── setUserIsGuest.js
│ │ ├── signingIn.js
│ │ ├── signingOut.js
│ │ └── updateUserDetail.js
├── Menu
│ ├── Menu.js
│ └── menu.scss
└── OverLoading
│ ├── OverLoading.js
│ ├── __action
│ └── toggleOverLoading.js
│ └── overLoading.scss
├── Partial
├── Router
│ └── Router.js
├── fetcher
│ ├── DefaultErrors.js
│ ├── clientFetcher.js
│ ├── fetcher.js
│ └── serverFetcher.js
└── skeleton
│ ├── debugLog.js
│ ├── skeleton.js
│ ├── skeletonClientProvider.js
│ └── skeletonServerProvider.js
├── render
├── Template
│ ├── Error.js
│ └── Index.js
├── client.js
└── server
│ ├── fetchProvider.js
│ ├── initialize.js
│ ├── render.js
│ └── server.js
└── setup
├── api.js
├── axiosConfig.js
├── browserHistory.js
├── constant.js
├── localStorage.js
├── route.js
├── routeMap.js
├── store.js
├── style
├── public.scss
└── var.scss
└── utility
├── badConnectionAlert.js
├── convertErrorToResponse.js
├── errorLogger.js
├── fetching.js
├── isErrorData.js
├── isValidUser.js
├── jumpScrollToTop.js
├── random.js
├── responseValidation.js
└── samplejQueryPlugin.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | ["@babel/preset-env", {"useBuiltIns": false}]
5 | ],
6 | "plugins": [
7 | "@babel/plugin-transform-runtime",
8 | "@babel/plugin-proposal-object-rest-spread",
9 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
10 | ["@babel/plugin-proposal-class-properties", { "loose" : true }]
11 | ]
12 | }
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | FILE_VERSION_TYPE='time'
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | PORT=8000
2 | RSSR_REDUX_DEV_TOOLS=true
3 | RSSR_FETCHER_DEBUG=false
4 | RSSR_SKELETON_DEBUG=false
5 | API_HOST_IN_CLIENT='http://localhost:8000/fake-api'
6 | API_HOST_IN_SERVER='http://localhost:8000/fake-api'
7 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | PORT=3000
2 | HOST='0.0.0.0'
3 | RSSR_REDUX_DEV_TOOLS=false
4 | RSSR_FETCHER_DEBUG=false
5 | RSSR_SKELETON_DEBUG=false
6 | API_HOST_IN_CLIENT='http://localhost:3000/fake-api'
7 | API_HOST_IN_SERVER='http://localhost:3000/fake-api'
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | RSSR_REDUX_DEV_TOOLS=false
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "globals": {
4 | "$": true,
5 | "jQuery": true
6 | },
7 | "rules": {
8 | "default-case": "off"
9 | }
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 |
4 | .DS_Store
5 | .sass-cache
6 | .idea
7 |
8 |
9 | package-lock.json
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
--------------------------------------------------------------------------------
/FakeApi/fakeApi.js:
--------------------------------------------------------------------------------
1 | const fakeApiData = {
2 | "posts": [
3 | {
4 | "id": 0,
5 | "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
6 | "body": "quia et suscipit. suscipit recusandae consequuntur expedita et cum. reprehenderit molestiae ut ut quas totam. nostrum rerum est autem sunt rem eveniet architecto"
7 | },
8 | {
9 | "id": 1,
10 | "title": "qui est esse",
11 | "body": "est rerum tempore vitae. sequi sint nihil reprehenderit dolor beatae ea dolores neque. fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis. qui aperiam non debitis possimus qui neque nisi nulla"
12 | },
13 | {
14 | "id": 2,
15 | "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
16 | "body": "et iusto sed quo iure. voluptatem occaecati omnis eligendi aut ad. voluptatem doloribus vel accusantium quis pariatur. molestiae porro eius odio et labore et velit aut"
17 | },
18 | {
19 | "id": 3,
20 | "title": "eum et est occaecati",
21 | "body": "ullam et saepe reiciendis voluptatem adipisci. sit amet autem assumenda provident rerum culpa. quis hic commodi nesciunt rem tenetur doloremque ipsam iure. quis sunt voluptatem rerum illo velit"
22 | },
23 | {
24 | "id": 4,
25 | "title": "nesciunt quas odio",
26 | "body": "repudiandae veniam quaerat sunt sed. alias aut fugiat sit autem sed est. voluptatem omnis possimus esse voluptatibus quis. est aut tenetur dolor neque"
27 | },
28 | {
29 | "id": 5,
30 | "title": "dolorem eum magni eos aperiam quia",
31 | "body": "ut aspernatur corporis harum nihil quis provident sequi. mollitia nobis aliquid molestiae. perspiciatis et ea nemo ab reprehenderit accusantium quas. voluptate dolores velit et doloremque molestiae"
32 | }
33 | ],
34 | "skeleton": {
35 | "dailyMessage": "Be happy! Life is too short."
36 | },
37 | "signin": {
38 | "token": "salkhfasoidpaskdpksapdksakdpisapdiasdphdpksapdksakdpisapdiasdphdpksapdksakdpisapdiasdphdioashdoihsaoid"
39 | },
40 | "signup": {
41 | "token": "salkhfasoidpaskdpksapdksakdpisapdiasdphdpksapdksakdpisapdiasdphdpksapdksakdpisapdiasdphdioashdoihsaoid"
42 | },
43 | "userDetails": {
44 | "firstName": "dan",
45 | "lastName": "abramov",
46 | "phone": "+123456789",
47 | "email": "dan.abramov@gmail.com"
48 | },
49 | "forgetPassword": {
50 | "message": "mail sent successfully. check your eamil."
51 | },
52 | "resetPasswordTrust": {
53 | "message": "token is valid."
54 | },
55 | "resetPasswordSubmit": {
56 | "message": "token is valid."
57 | }
58 | }
59 |
60 |
61 |
62 | module.exports = function (app) {
63 |
64 | app.use('/fake-api/:name/:id?', function (req, res) {
65 | const {name, id} = req.params;
66 | const delay = req.query.delay || 1
67 | let result;
68 |
69 | if (name)
70 | result = fakeApiData[name]
71 |
72 | if (id) {
73 | if (Array.isArray(result)) {
74 | const idResult = result.some(function (item) {
75 | const isEqual = String(item.id) === id;
76 |
77 | if (isEqual)
78 | result = item
79 |
80 | return isEqual;
81 | })
82 |
83 | if (!idResult)
84 | result = undefined;
85 | } else {
86 | result = undefined;
87 | }
88 | }
89 |
90 | if (result)
91 | setTimeout(function () {
92 | res.status(200).json(result)
93 | },delay)
94 | else
95 | res.status(404).send('not found')
96 |
97 | })
98 | }
--------------------------------------------------------------------------------
/FakeApi/help.txt:
--------------------------------------------------------------------------------
1 | ::: FAKE API :::
2 | We connected the Fake-API to the RSSR so you can see how it actually works.
3 |
4 | >>> HOW CAN RUN IT
5 | with 'npm run fake' you can launch it.
6 |
7 | >>> HOW CAN USE IT in code
8 | we imported fake api in sever files, so you can assess from 'http://localhost:8000/fake-api' in development and port 3000 for production.
9 |
10 |
11 | >>> HOW TO REMOVE IT:
12 | step1: remove ~/FakeApi directory.
13 | step2: change API_HOST_IN_CLIENT and API_HOST_IN_SERVER in ~/.env.development and ~/.env.production files to your real API address.
14 | step3: go to ~/server/development and ~/server/production files and remve fake api require (Marked with a symbol).
15 | step4: go to ~/src/setup/api.js and set your real routes. NOTICE: before remove or change this routes please find 'api.[ROUTE-NAME]' like 'api.forgetPassword' and do the necessary work.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | # RSSR Boilerplate
8 | Welcome to RSSR(React-JS Server Side Rendering). Being here is a sign of your professionalism.
9 |
10 | RSSR is a SSR boilerplate for React js and contains:
11 | - SSR (Server Side Rendering)
12 | - User Authentication Structure
13 | - SEO optimization utilities
14 | - SCSS Style Namespace
15 | - and more …
16 |
17 | ## contain
18 | :: Base
19 | - React 17.0.1 (react-dom 17.0.1)
20 | - express 4.17.1
21 | - webpack 4.43.0
22 | - eslint 6.8.0
23 | - axios 0.21.0
24 | - history 4.10.1
25 |
26 | :: Useful side
27 | - node-sass 4.14.1 (support scss)
28 | - rssr-seo-optimization 0.0.1 (improve SEO)
29 | - dotenv 8.2.0 (support .env files)
30 | - cookie-parser 1.4.5 (support cookie in server mode)
31 | - express-rate-limit 5.1.3 (limit and filer requests in server)
32 | - local-storage 2.0.0 (good structuer for local storage)
33 | - trim-redux 2.3.0 (Redax simplification)
34 | - rssr-namespace 1.0.1 (set name space for SCSS (style) files.)
35 |
36 | :: utility (there is no force, You can simply delete)
37 | - bootstrap 4.5.3
38 | - jquery 3.5.1
39 |
40 | ## Documentation
41 | See [Documentation](https://github.com/rssr-org/RSSR-Documentation) in github.
42 |
43 | You can also watch videos of RSSR team at [aparat](https://www.aparat.com/user/video/user_list/userid/722589/usercat/413997) and [youtube](https://www.youtube.com/channel/UCNkuorlYEWReSMglMp25yCw), .
44 |
45 | ## Usage Notice
46 | The core of RSSR is stable but needs some changes before it can be released publicly. You can fork, review and star it but DO NOT USE it for your enterprise projects until the final release!
47 |
48 | For more information, follow us at : [Telegram channel](https://t.me/rssr_org).
49 |
50 |
51 | ## Know more
52 |
53 | #### what is SSR?
54 | Server Side Rendering is a popular technique for rendering a normally
55 | client-side single page app (SPA) on the server and then sending
56 | a fully rendered page to the client. The client’s JavaScript bundle
57 | can then take over and the SPA can operate as normal. One major
58 | benefit of using SSR is in having an app that can be crawled
59 | for its content even for crawlers that don’t execute JavaScript code.
60 | This can help with SEO and with providing meta data to social media channels.
61 |
62 |
63 | #### What is a Boilerplate?
64 | In programming, the term boilerplate code refers to blocks of code used over and over again.
65 |
66 | Let’s assume your development stack consists of several libraries,
67 | such as React, Babel, Express, Jest, Webpack, etc. When you
68 | start a new project, you initialize all these libraries
69 | and configure them to work with each other.
70 |
71 | With every new project that you start, you will be repeating yourself.
72 | You could also introduce inconsistencies in how these libraries
73 | are set up in each project. This can cause confusion when you
74 | switch between projects.
75 |
76 | This is where boilerplates come in. A boilerplate is a template that
77 | you can clone and reuse for every project.
78 |
79 |
80 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RSSR",
3 | "version": "0.2.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/rssr-org/RSSR.git"
7 | },
8 | "author": "RSSR-ORG",
9 | "license": "MIT",
10 | "bugs": {
11 | "url": "https://github.com/rssr-org/RSSR/issues"
12 | },
13 | "homepage": "https://github.com/rssr-org/RSSR#readme",
14 | "scripts": {
15 | "dev": "node ./provider/server/development.js",
16 | "build": "webpack --config ./provider/webpack/production.js --debug --progress --display-error-details --profile --colors",
17 | "start": "node ./provider/server/production.js",
18 | "start-pm2-low": "pm2 start ./provider/server/production.js --name RSSR",
19 | "start-pm2": "npm run start-pm2-low -- -i max",
20 | "up-low": "pm2 delete RSSR & npm run start-pm2-low",
21 | "up": "pm2 delete RSSR & npm run start-pm2",
22 | "lint": "eslint src",
23 | "test": "echo 'do test'"
24 | },
25 | "dependencies": {
26 | "axios": "^0.21.0",
27 | "cookie-parser": "^1.4.5",
28 | "dotenv": "^8.2.0",
29 | "express": "^4.17.1",
30 | "express-rate-limit": "^5.1.3",
31 | "rssr-seo-optimization": "0.0.1",
32 | "serialize-javascript": "^5.0.1"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "^7.12.9",
36 | "@babel/plugin-proposal-class-properties": "^7.12.1",
37 | "@babel/plugin-proposal-decorators": "^7.12.1",
38 | "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
39 | "@babel/plugin-transform-runtime": "^7.12.1",
40 | "@babel/preset-env": "^7.12.7",
41 | "@babel/preset-react": "^7.12.7",
42 | "babel-eslint": "^10.1.0",
43 | "babel-loader": "^8.2.2",
44 | "bootstrap": "^4.5.3",
45 | "css-hot-loader": "^1.4.4",
46 | "css-loader": "^3.5.3",
47 | "dotenv-webpack": "^1.7.0",
48 | "eslint": "^7.5.0",
49 | "eslint-config-react-app": "^6.0.0",
50 | "eslint-loader": "^4.0.2",
51 | "eslint-plugin-flowtype": "^5.2.0",
52 | "eslint-plugin-import": "^2.22.0",
53 | "eslint-plugin-jsx-a11y": "^6.3.1",
54 | "eslint-plugin-react": "^7.20.3",
55 | "eslint-plugin-react-hooks": "^4.0.8",
56 | "expose-loader": "^0.7.5",
57 | "history": "^4.10.1",
58 | "ignore-loader": "^0.1.2",
59 | "jquery": "^3.5.1",
60 | "js-cookie": "^2.2.1",
61 | "local-storage": "^2.0.0",
62 | "mini-css-extract-plugin": "^0.9.0",
63 | "node-sass": "^4.14.1",
64 | "optimize-css-assets-webpack-plugin": "^5.0.3",
65 | "popper.js": "^1.16.1",
66 | "prop-types": "^15.7.2",
67 | "querystringify": "^2.1.1",
68 | "react": "^17.0.1",
69 | "react-dom": "^17.0.1",
70 | "react-helmet-async": "^1.0.6",
71 | "react-lazy-load-image-component": "^1.4.3",
72 | "react-router-dom": "^5.1.2",
73 | "react-toastify": "^5.5.0",
74 | "react-tooltip": "^4.2.5",
75 | "rssr-namespace": "^1.0.1",
76 | "rssr-open-browser": "^1.0.0",
77 | "sass-loader": "^8.0.2",
78 | "terser-webpack-plugin": "^2.3.6",
79 | "trim-redux": "^2.3.0",
80 | "webpack": "^4.43.0",
81 | "webpack-cli": "^3.3.11",
82 | "webpack-dev-middleware": "^3.7.2",
83 | "webpack-hot-middleware": "^2.25.0",
84 | "webpack-hot-server-middleware": "^0.6.0"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/provider/server/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'development';
2 | require('../setup/envLoader')
3 | require('../setup/fileVersion')
4 |
5 | const cookieParser = require('cookie-parser')
6 | const express = require('express')
7 | const webpack = require('webpack')
8 | const config = require('../webpack/development')
9 | const webpackDevMiddleware = require('webpack-dev-middleware')
10 | const webpackHotMiddleware = require('webpack-hot-middleware')
11 | const webpackHotServerMiddleware = require('webpack-hot-server-middleware')
12 | const {DIST_ROUTE, PUBLIC_NAME} = require('../setup/constant')
13 |
14 |
15 |
16 |
17 |
18 |
19 | // express app
20 | const app = express()
21 |
22 | //----- REMOVE THIS PART AND 'fakeApi.js' FILE IN REAL PROJECTS -----//
23 | require('../../FakeApi/fakeApi')(app)
24 | //-------------------------------------------------------------------//
25 |
26 | // cookie
27 | app.use(cookieParser())
28 |
29 | // static files
30 | app.use(express.static(PUBLIC_NAME))
31 |
32 | // create webpack compiler
33 | const compiler = webpack(config)
34 |
35 | // make bundled project source files accessible from memory
36 | app.use(webpackDevMiddleware(compiler, {
37 | publicPath: DIST_ROUTE,
38 | serverSideRender: true
39 | }))
40 |
41 | // recompile webpack when file changes
42 | app.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')))
43 |
44 | // hot update Webpack bundles on the server
45 | app.use(webpackHotServerMiddleware(compiler))
46 |
47 |
48 |
49 |
50 |
51 | // run server
52 | const PORT = process.env.PORT || 8000;
53 |
54 | app.listen(PORT, error => {
55 | if (error) {
56 | return console.error('Error in server/development.js: ', error);
57 | } else {
58 | console.log(`development server running at http://localhost:${PORT}`);
59 | }
60 | })
61 |
--------------------------------------------------------------------------------
/provider/server/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'production';
2 | require('../setup/envLoader')
3 | require('../setup/fileVersion')
4 |
5 | const {DIST_PATH, DIST_ROUTE, PUBLIC_NAME, SERVER_DIST_PATH} = require('../setup/constant')
6 | const cookieParser = require('cookie-parser')
7 | const seoOptimization = require('rssr-seo-optimization')
8 | const rateLimit = require('../setup/rateLimit')
9 | const express = require('express')
10 | const serverRenderer = require(SERVER_DIST_PATH).default
11 |
12 |
13 |
14 |
15 |
16 | // express app
17 | const app = express()
18 |
19 | //----- REMOVE THIS PART AND 'fakeApi.js' FILE IN REAL PROJECTS -----//
20 | require('../../FakeApi/fakeApi')(app)
21 | //-------------------------------------------------------------------//
22 |
23 | // cookie
24 | app.use(cookieParser())
25 |
26 | // make bundled final project source files accessible
27 | app.use(DIST_ROUTE, express.static(DIST_PATH))
28 |
29 | // load static files
30 | app.use(express.static(PUBLIC_NAME))
31 |
32 | // Redirect from www to non-www and remove slash at the end of URL
33 | seoOptimization(app)
34 |
35 | // limit the request number of each user in 'windowMs' milliseconds
36 | rateLimit(app)
37 |
38 | // load server script and render app (do react SSR)
39 | app.use(serverRenderer())
40 |
41 |
42 |
43 |
44 |
45 | // run server
46 | const PORT = process.env.PORT || 3000
47 | const HOST = process.env.HOST || '0.0.0.0'
48 |
49 | app.listen(PORT, HOST, error => {
50 | if (error)
51 | return console.error('Error in server/production.js: ', error);
52 | else
53 | console.log(`production server running at http://localhost:${PORT} and ${HOST} host.`);
54 | })
55 |
--------------------------------------------------------------------------------
/provider/setup/constant.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const C = {};
4 |
5 | // dist
6 | C.DIST_NAME = 'dist';
7 | C.DIST_ROUTE = '/' + C.DIST_NAME;
8 | C.DIST_PATH = path.resolve(process.cwd(), '.' + C.DIST_ROUTE);
9 |
10 | // client
11 | C.CLIENT_NAME = 'client.js';
12 | C.CLIENT_ROUTE = './src/render/' + C.CLIENT_NAME;
13 |
14 | // server
15 | C.SERVER_NAME = 'server.js';
16 | C.SERVER_ROUTE = './src/render/server/' + C.SERVER_NAME;
17 | C.SERVER_DIST_PATH = path.resolve(C.DIST_PATH, C.SERVER_NAME);
18 |
19 | // public
20 | C.PUBLIC_NAME = 'public';
21 |
22 | // style
23 | C.SCSS_PATH = path.resolve(process.cwd(), './src/setup/style');
24 |
25 | // Development > open browser
26 | C.OPEN_BROWSER_URL = 'http://localhost:' + process.env.PORT || 8000;
27 |
28 | module.exports = C
--------------------------------------------------------------------------------
/provider/setup/envLoader.js:
--------------------------------------------------------------------------------
1 | // load environment variable of .env file
2 | const dotenv = require('dotenv')
3 | dotenv.config()
4 |
5 | // load environment variable of .env.[NODE_ENV] files
6 | const fs = require('fs')
7 | const envPath = fs.readFileSync('.env.' + process.env.NODE_ENV);
8 | const envConfig = dotenv.parse(envPath)
9 | for (const k in envConfig) {
10 | process.env[k] = envConfig[k]
11 | }
12 |
--------------------------------------------------------------------------------
/provider/setup/fileVersion.js:
--------------------------------------------------------------------------------
1 |
2 | // define global.FILE_VERSION for dist file version. see render/Index.js template.
3 | // global.FILE_VERSION is 'npm' or 'random' or 'disable'
4 | switch (process.env.FILE_VERSION_TYPE) {
5 | case 'npm':
6 | // define global.FILE_VERSION for dist file version. see render/Index.js template.
7 | const npmVersion = require("../../package").version;
8 | // value of npm package.js verion property
9 | global.FILE_VERSION = '?v=' + npmVersion;
10 | break;
11 | case 'time':
12 | // time stamp of now
13 | const timeStampVersion = new Date().getTime();
14 | // random 24 char string
15 | global.FILE_VERSION = '?v=' + timeStampVersion;
16 | break;
17 | case 'disable':
18 | // without version
19 | global.FILE_VERSION = '';
20 | break;
21 | default:
22 | console.error('process.env.FILE_VERSION is not valid!', global.FILE_VERSION)
23 | }
24 |
--------------------------------------------------------------------------------
/provider/setup/rateLimit.js:
--------------------------------------------------------------------------------
1 | // limit request number
2 | const rateLimit = require("express-rate-limit");
3 |
4 | /**
5 | * rateLimit
6 | *
7 | * limit the request number of each user in windowMs
8 | * read more: https://www.npmjs.com/package/express-rate-limit
9 | *
10 | * @param app