├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .storybook
├── addons.js
├── config.js
└── storybook.css
├── .travis.yml
├── LICENSE
├── README.md
├── appveyor.yml
├── config
├── custom-environment-variables.json
├── default.js
├── production.js
└── test.js
├── cypress.json
├── docs
└── react-head-start-logo.jpg
├── logs
└── logs.md
├── package-lock.json
├── package.json
├── src
├── client
│ ├── App.jsx
│ ├── App.scss
│ ├── codeSplitMappingsAsync.js
│ ├── codeSplitMappingsSync.js
│ ├── components
│ │ ├── common
│ │ │ ├── ErrorMessage
│ │ │ │ ├── ErrorMessage.js
│ │ │ │ ├── ErrorMessage.stories.js
│ │ │ │ ├── ErrorMessage.test.js
│ │ │ │ └── _ErrorMessage.scss
│ │ │ ├── Loading
│ │ │ │ ├── Loading.js
│ │ │ │ ├── Loading.stories.js
│ │ │ │ ├── Loading.test.js
│ │ │ │ └── _Loading.scss
│ │ │ ├── MainFooter
│ │ │ │ ├── MainFooter.jsx
│ │ │ │ ├── MainFooter.stories.js
│ │ │ │ └── _MainFooter.scss
│ │ │ ├── MainHeader
│ │ │ │ ├── MainHeader.jsx
│ │ │ │ ├── MainHeader.stories.js
│ │ │ │ └── _MainHeader.scss
│ │ │ └── MainLayout
│ │ │ │ ├── MainLayout.jsx
│ │ │ │ ├── MainLayout.stories.js
│ │ │ │ └── _MainLayout.scss
│ │ └── index.js
│ ├── global-scss
│ │ ├── _common.scss
│ │ ├── _reset.scss
│ │ └── _vars.scss
│ ├── index.js
│ ├── index.scss
│ ├── introduction.stories.js
│ ├── pages
│ │ ├── ErrorPage
│ │ │ ├── ErrorPage.jsx
│ │ │ ├── ErrorPage.stories.js
│ │ │ ├── ErrorPage.test.js
│ │ │ └── _ErrorPage.scss
│ │ ├── HomePage
│ │ │ ├── HomePage.jsx
│ │ │ ├── HomePage.stories.js
│ │ │ ├── HomePage.test.js
│ │ │ └── _HomePage.scss
│ │ └── UsersPage
│ │ │ ├── UsersPage.jsx
│ │ │ ├── UsersPage.stories.js
│ │ │ ├── UsersPage.test.js
│ │ │ └── _UsersPage.scss
│ ├── reducers
│ │ ├── index.js
│ │ └── users.js
│ ├── routes.js
│ └── utils
│ │ ├── api.js
│ │ ├── clientConfig.js
│ │ └── polyfills.js
├── server
│ ├── index.js
│ ├── routes
│ │ ├── apiRoute.js
│ │ ├── renderPageRoute.js
│ │ └── renderPageRoute.test.js
│ ├── utils
│ │ ├── banner.js
│ │ ├── logger.js
│ │ ├── outgoingRequestLogger.js
│ │ └── terminalColors.js
│ └── views
│ │ └── 500.html
└── static
│ ├── favicon.ico
│ └── robots.txt
└── test
├── e2e-test
├── fixtures
│ └── example.json
├── plugins
│ └── index.js
├── support
│ ├── commands.js
│ └── index.js
└── tests
│ └── simple_spec.js
└── unit-test
├── jest.config.js
├── jest.polyfills.js
└── jest.unit-test.init.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react"],
3 | "plugins": ["transform-class-properties", "syntax-dynamic-import"]
4 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | //need babel-eslint to allow class static props
3 | "parser": "babel-eslint",
4 | "extends": [ "eslint:recommended", "plugin:react/recommended" ],
5 | "plugins": [ "react", "jest" ],
6 | "env": {
7 | "browser": true,
8 | "commonjs": true,
9 | "es6": true,
10 | "node": true,
11 | "jest/globals": true
12 | },
13 | "globals":{
14 | //unit tests
15 | "expect":true,
16 | "shallow":true,
17 | "render":true,
18 | "mount":true,
19 | "jest":true,
20 | "beforeAll":true,
21 | //e2e tests
22 | "cy":true,
23 | },
24 | "parserOptions": {
25 | "ecmaFeatures": {
26 | "experimentalObjectRestSpread": true,
27 | "jsx": true
28 | },
29 | "sourceType": "module"
30 | },
31 | "rules": {
32 | //general
33 | "indent": [ "error", 2 ],
34 | "quotes": [ "error", "single" ],
35 | "semi": [ "error", "always" ],
36 | "no-console": ["error", { allow: ["warn", "error"] }],
37 | //react
38 | "react/react-in-jsx-scope": "off",
39 | //jest
40 | "jest/no-disabled-tests": "warn",
41 | "jest/no-focused-tests": "error",
42 | "jest/no-identical-title": "error",
43 | "jest/prefer-to-have-length": "warn",
44 | "jest/valid-expect": "error"
45 | }
46 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | logs/*.log
4 | coverage
5 | .cache
6 | config/local.js
7 | test/e2e-test/videos
8 | test/e2e-test/screenshots
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | //main config for storybook
2 |
3 | import { configure } from '@storybook/react';
4 | import { setOptions } from '@storybook/addon-options';
5 | //note: we depend on our parcel build of sass (see the npm script "storybook" for the css build)
6 | import '../dist/bundles/index.css';
7 | import 'github-markdown-css';
8 | import './storybook.css'
9 |
10 | setOptions({
11 | sortStoriesByKind: true
12 | });
13 |
14 | // automatically import all files ending in *.stories.js
15 | const req = require.context('../src/client', true, /.stories.js$/);
16 | function loadStories() {
17 | //Note: sortStoriesByKind doesnt seem to work so force intro story first
18 | req('./introduction.stories.js');
19 |
20 | req.keys()
21 | .forEach((filename) => req(filename));
22 | }
23 |
24 | configure(loadStories, module);
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.storybook/storybook.css:
--------------------------------------------------------------------------------
1 | /* custom styles used in storybook */
2 |
3 | .markdown-body{
4 | margin: 0 auto;
5 | max-width: 1024px;
6 | padding: 20px;
7 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 gregtillbrook
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | A React App bootstrap with Server Side Rendering, Code Splitting, Hot Reloading, Redux and React Router.
4 |
5 | [![Linux CI Build][travis-image]][travis-url]
6 | [![Windows CI Build][appveyor-image]][appveyor-url]
7 | [![Known Vulnerabilities][snyk-image]][snyk-url]
8 | [![Dependency Up-to-dateness][david-image]][david-url]
9 |
10 |
11 | A rich performant react app can have a lot of fiddly parts once you start adding things like SSR(Server Side Rendering), Code Splitting & Hot loading. You also tend to want similar core setups in each new app you make. This bootstrap aims to capture that setup in a re-usable form and do it in a way that is as clean and intuitive as is (reasonably) possible.
12 |
13 |
14 |
15 | # Highlights
16 |
17 | ### 🚀 All the react goodness
18 | - [React v16](https://www.npmjs.com/package/react), [Redux](https://www.npmjs.com/package/redux), [React Router v4](https://www.npmjs.com/package/react-router) provide the core
19 | - ES2017+ standard modern javascript
20 | - Server is [express v4](https://www.npmjs.com/package/express) (running on Node 8 and up)
21 |
22 | ### 📦 Production ready
23 | - Production ready app bundled with Parcel i.e. no webpack. Its lightning quick & and zero configuration so no need to maintain a webpack (or grunt or gulp) config file
24 | - [Code splitting](https://parceljs.org/code_splitting.html) hooks to optimise downloaded bundle size
25 | - Server side rendering to provide best SEO and load performance
26 | - Logging (to file & console) with [winston](https://www.npmjs.com/package/winston)
27 | - Easy app config in different environments with [config](https://www.npmjs.com/package/config)
28 |
29 | ### 🐵 Dev
30 | - Instant updates in browser following code changes with Hot Reloading (aka HMR or [Hot Module Replacement](https://parceljs.org/hmr.html))
31 | - Auto reloading of node upon server code changes thanks to [Nodemon](https://www.npmjs.com/package/nodemon)
32 | - Interactive development/debugging/testing of UI components with [Storybook](https://storybook.js.org/)
33 | - Basic SCSS styling in place to extend with what you need or replace with your css-in-js solution of choice
34 | - Sensible (opiniated) project structure for modular component dev. The code/styling/tests/etc for component are all in the same folder
35 | - Watch task to lint and unit test continuously on code changes
36 |
37 | ### ⛑ Code Quality
38 | - Syntax checking (aka linting) with [eslint](https://www.npmjs.com/package/eslint)
39 | - Unit testing with [jest](http://facebook.github.io/jest/)
40 | - End to end/integration testing with [cypress](https://www.cypress.io/)
41 |
42 |
43 |
44 | # Getting started
45 |
46 | ```console
47 | git clone git@github.com:gregtillbrook/react-head-start.git my-react-app
48 | cd my-react-app
49 | npm install
50 | npm run dev
51 | ```
52 |
53 |
54 | # Key npm scripts
55 | The npm scripts may look confusing at first but most arent meant to be called directly but get called as part of other scripts. The key scripts to know are;
56 |
57 | ```npm run dev```
58 | Standard dev command. Runs up the app in 'dev' mode with auto reloading on server code changes + hot reloading of client.
59 |
60 | ```npm run prod```
61 | Builds app in production mode (minified & faster than dev mode) and serves prod app. If you need to build and serve indepentally (which you probably do) use ```npm run prod:build``` and ```npm run prod:serve```. Note: prod:build is broken down into several sub tasks (client build, server build, copy files to dist folder) for readability but you wont need to run those individually.
62 |
63 | ```npm test```
64 | Runs linting, unit tests and end to end tests. This is what the CI build runs and is good practice to run before each commit. You can also individually run ```npm run lint``` ```npm run test:unit``` and ```npm run test:e2e```.
65 |
66 | ```npm run test:watch```
67 | Runs a watch task that runs linting and unit tests each time the code changes. This is a useful one to run alongside ```npm run dev``` while developing. Note: the e2e tests are omitted from this by default because they tend to run a little slower in normal sized apps.
68 |
69 | ```npm run test:e2e:dev```
70 | Use this when authoring e2e tests. ```npm run test:e2e``` runs the tests in headless mode and handles the build + running of the app which is great for test runs and CI but is not optimal for developing and debugging. To develope e2e tests
71 | - In a terminal window, start up the app with ```npm run prod```
72 | - In a second terminal window, open the cypress UI with ```npm run test:e2e:dev```
73 |
74 | ```npm run storybook```
75 | For interactive development/debugging/testing of components. You can also use ```npm run storybook:build``` to generate a static storybook site
76 |
77 | ```npm run todo```
78 | A little helper to console log a list of all TODO notes in the app.
79 |
80 |
81 |
82 | # Things to think about
83 |
84 | ### You may not need code splitting
85 | The value of code splitting depends a lot on the structure of your app. If you have a large app with many distinct but rich pages, code splitting could be incredibly valuable. However if your app is small or HAS to load the bulk of your code in the initial load, then code splitting may not be so useful.
86 |
87 | ### You may not need server side rendering
88 | Historically, server side rendering has been important for things like SEO (search engines would index static html only) and page load performance. But nowadays these things are less of an issue e.g. google now executes javascript when indexing a page (although other search engines still vary in js support). So again, your apps structure and requirements will dictate how valuable SSR is to you.
89 |
90 | ### This thing has a lot of crap I don't want
91 | React bootstraps tend to be opiniated by their nature. The trick is hitting the balance between adding stuff thats useful and bloating it with useless stuff that makes the starter app harder to understand. What do you think - is there something important missing? or have I included something that really shouldnt be here?
92 |
93 | ### Where's webpack?
94 | Webacks configurability is great for complex apps or those requiring very specific builds. If you can avoid having to write/maintain a webpack config then thats a win - so I preferr to start with Parcel or create-react-app and swap to full webpack only when needed.
95 |
96 | ### Lots of comments?
97 | Ive tried to comment this app quite heavily so it's easier to understand what's going on as you step through the code. You'll probably what to clear a lot of those comments out when you start using the app. Is there anything confusing that could use more documentation?
98 |
99 |
100 |
101 | # TODO
102 | Stuff not done yet that Im considering adding;
103 | - code coverage reporting (+ make sure this bootstrap starts from 100% coverage)
104 | - service worker for [Progressive Web App](https://developers.google.com/web/progressive-web-apps/)
105 | - [prettier](https://www.npmjs.com/package/prettier)
106 | - Internationalisation
107 |
108 |
109 | [travis-image]: https://img.shields.io/travis/gregtillbrook/react-head-start/master.svg?label=Linux%20CI%20Build
110 | [travis-url]: https://travis-ci.org/gregtillbrook/react-head-start
111 | [appveyor-image]: https://img.shields.io/appveyor/ci/gregtillbrook/react-head-start/master.svg?label=Windows%20CI%20Build
112 | [appveyor-url]: https://ci.appveyor.com/project/gregtillbrook/react-head-start
113 | [snyk-image]: https://snyk.io/test/github/gregtillbrook/react-head-start/badge.svg
114 | [snyk-url]: https://snyk.io/test/github/gregtillbrook/react-head-start
115 | [david-image]: https://david-dm.org/gregtillbrook/react-head-start.svg
116 | [david-url]: https://david-dm.org/gregtillbrook/react-head-start
117 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '8'
4 | install:
5 | - ps: Install-Product node $env:nodejs_version
6 | - set CI=true
7 | - npm install --global npm@latest
8 | - set PATH=%APPDATA%\npm;%PATH%
9 | - npm install
10 | matrix:
11 | fast_finish: true
12 | build: off
13 | shallow_clone: true
14 | test_script:
15 | - node --version
16 | - npm --version
17 | - npm test
18 | cache:
19 | - '%APPDATA%\npm-cache'
--------------------------------------------------------------------------------
/config/custom-environment-variables.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": "NODE_PORT",
3 | "host": "NODE_HOST"
4 | }
5 |
--------------------------------------------------------------------------------
/config/default.js:
--------------------------------------------------------------------------------
1 | //put all app configuration here with a sensible default and then override per machine/environment/etc in other config files
2 | //more docs on how config files work = https://github.com/lorenwest/node-config/wiki/Configuration-Files
3 | //note: temporary local config (for local debugging/development) can be put into config/local.js (it's git ignored)
4 | module.exports = {
5 | host: undefined,
6 | port: 5000,
7 |
8 | //config inside here will be available in the client browser app
9 | clientConfig:{
10 | //WARNING: dont put anything sensitive in here - it WILL be publicly visible in the client browser
11 | apiHost: 'http://localhost:5000',
12 | },
13 |
14 | enableServerSideRender:true,
15 |
16 | logIncomingHttpRequests:true,
17 | logOutgoingHttpRequests:true,
18 |
19 | logging:{
20 | consoleLogLevel:'debug',
21 | logFileLogLevel:'info',
22 | logFilePath:'./logs/server.log',
23 | maxLogFileSizeInMB:5,
24 | maxLogFileCount:5
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/config/production.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | //put production configuration here
3 | };
4 |
--------------------------------------------------------------------------------
/config/test.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | //put override config for jest unit tests here
3 |
4 | port: 5010,
5 | clientConfig:{
6 | apiHost: 'http://localhost:5010',
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "fixturesFolder":"test/e2e-test/fixtures",
3 | "integrationFolder":"test/e2e-test/tests",
4 | "pluginsFile":"test/e2e-test/plugins/index.js",
5 | "screenshotsFolder":"test/e2e-test/screenshots",
6 | "supportFile":"test/e2e-test/support/index.js",
7 | "videosFolder":"test/e2e-test/videos"
8 | }
--------------------------------------------------------------------------------
/docs/react-head-start-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gregtillbrook/react-head-start/c0d0a8838ab32e30bf70025d8d05bde477f0f3cb/docs/react-head-start-logo.jpg
--------------------------------------------------------------------------------
/logs/logs.md:
--------------------------------------------------------------------------------
1 | This is the default location for winston server logs (configurable in config files)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-head-start",
3 | "version": "0.9.0",
4 | "description": "React App bootstrap with Server Side Rendering, Code Splitting, Hot Reloading, Redux and React Router",
5 | "author": "Greg Tillbrook",
6 | "engines": {
7 | "node": ">=8"
8 | },
9 | "scripts": {
10 | "start": "npm run dev",
11 | "dev": "npm-run-all --parallel dev:build dev:serve",
12 | "prod": "npm run prod:build && npm run prod:serve",
13 | "clean": "rimraf dist",
14 | "dev:build": "npm run clean && cross-env NODE_ENV=development parcel watch src/client/index.js --out-dir dist/bundles",
15 | "dev:build:css": "npm run clean && cross-env NODE_ENV=development parcel watch src/client/index.scss --out-dir dist/bundles",
16 | "dev:serve": "cross-env NODE_ENV=development npx nodemon --exec babel-node --ext js,jsx --delay 0.5 --watch src --watch test src/server/",
17 | "prod:build": "npm run clean && npm run prod:copy-files && npm run prod:build-server && npm run prod:build-client ",
18 | "prod:build-client": "cross-env NODE_ENV=production parcel build src/client/index.js --public-url / --out-dir dist/bundles --detailed-report",
19 | "prod:build-server": "cd src/ && cross-env npx babel '**/*.{js,jsx}' --no-babelrc --presets react,node8 --plugins transform-class-properties,syntax-dynamic-import --out-dir '../dist' --ignore test.js",
20 | "prod:copy-files": "cross-env npx copyfiles -u 1 'src/static/**/*' dist",
21 | "prod:serve": "cross-env NODE_ENV=production node dist/server/",
22 | "test": "npm run lint && npm run test:unit && npm run test:e2e",
23 | "lint": "npx eslint --ext jsx,js src test",
24 | "test:unit": "npx jest --config test/unit-test/jest.config.js",
25 | "test:e2e": "npm run prod:build && cross-env NODE_ENV=test node-while -s dist/server/index.js -r \"node node_modules/cypress/bin/cypress run\"",
26 | "test:e2e:dev": "npx cypress open --env env=production",
27 | "test:watch": "npm-watch",
28 | "test:quick": "npm run lint && npm run test:unit",
29 | "todo": "npx fixme -i 'node_modules/**' -i '.git/**' -i 'dist/**' --skip note",
30 | "storybook": "npm-run-all --parallel dev:build:css storybook:serve",
31 | "storybook:serve": "wait-on ./dist/bundles/index.css && start-storybook -p 6006",
32 | "storybook:build": "build-storybook"
33 | },
34 | "browser": {
35 | "./src/client/codeSplitMappingsSync.js": "./src/client/codeSplitMappingsAsync.js",
36 | "config": "./src/client/utils/clientConfig.js"
37 | },
38 | "watch": {
39 | "test:quick": {
40 | "patterns": [
41 | "src",
42 | "test"
43 | ],
44 | "extensions": "js,jsx",
45 | "quiet": false
46 | }
47 | },
48 | "postcss": {
49 | "modules": false,
50 | "plugins": {
51 | "autoprefixer": {
52 | "browsers": [
53 | ">1%",
54 | "last 4 versions",
55 | "Firefox ESR",
56 | "not ie < 9"
57 | ],
58 | "flexbox": "no-2009"
59 | }
60 | }
61 | },
62 | "dependencies": {
63 | "caller-callsite": "^2.0.0",
64 | "compression": "^1.7.1",
65 | "config": "^1.28.1",
66 | "cookie-parser": "^1.4.3",
67 | "express": "^4.16.2",
68 | "morgan": "^1.9.0",
69 | "serve-favicon": "^2.4.5",
70 | "winston": "^2.3.1"
71 | },
72 | "devDependencies": {
73 | "@storybook/addon-actions": "^3.3.12",
74 | "@storybook/addon-links": "^3.3.12",
75 | "@storybook/addon-notes": "^3.3.12",
76 | "@storybook/addon-options": "^3.3.12",
77 | "@storybook/addons": "^3.3.12",
78 | "@storybook/react": "^3.3.12",
79 | "autoprefixer": "^7.2.1",
80 | "babel-cli": "^6.26.0",
81 | "babel-core": "^6.26.0",
82 | "babel-eslint": "^8.0.3",
83 | "babel-jest": "^21.2.0",
84 | "babel-loader": "^7.1.2",
85 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
86 | "babel-plugin-transform-class-properties": "^6.24.1",
87 | "babel-polyfill": "^6.26.0",
88 | "babel-preset-env": "^1.6.1",
89 | "babel-preset-node8": "^1.2.0",
90 | "babel-preset-react": "^6.24.1",
91 | "cheerio": "^1.0.0-rc.2",
92 | "copyfiles": "^1.2.0",
93 | "core-js": "^2.5.3",
94 | "cross-env": "^5.1.1",
95 | "cypress": "^1.4.0",
96 | "enzyme": "^3.2.0",
97 | "enzyme-adapter-react-16": "^1.1.0",
98 | "eslint": "^4.13.1",
99 | "eslint-plugin-jest": "^21.5.0",
100 | "eslint-plugin-react": "^7.5.1",
101 | "fixme": "^0.4.4",
102 | "github-markdown-css": "^2.10.0",
103 | "jest": "^21.2.1",
104 | "node-sass": "^4.7.2",
105 | "node-while": "^1.0.1",
106 | "nodemon": "^1.12.7",
107 | "npm-run-all": "^4.1.2",
108 | "npm-watch": "^0.3.0",
109 | "parcel-bundler": "^1.6.2",
110 | "parcel-plugin-bundle-visualiser": "^1.0.2",
111 | "prop-types": "^15.6.0",
112 | "react": "^16.2.0",
113 | "react-async-component": "^1.0.2",
114 | "react-dom": "^16.2.0",
115 | "react-helmet": "^5.2.0",
116 | "react-redux": "^5.0.6",
117 | "react-router-config": "^1.0.0-beta.4",
118 | "react-router-dom": "^4.2.2",
119 | "react-router-test-context": "^0.1.0",
120 | "redux": "^3.7.2",
121 | "redux-thunk": "^2.2.0",
122 | "regenerator-runtime": "^0.11.1",
123 | "rimraf": "^2.6.2",
124 | "storybook-router": "^0.3.2",
125 | "wait-on": "^2.1.0"
126 | },
127 | "keywords": [
128 | "react",
129 | "bootstrap",
130 | "head start",
131 | "redux",
132 | "react-router",
133 | "express",
134 | "ssr",
135 | "server side render",
136 | "code splitting",
137 | "hot loading",
138 | "boilerplate"
139 | ],
140 | "repository": "gregtillbrook/react-head-start",
141 | "license": "MIT"
142 | }
143 |
--------------------------------------------------------------------------------
/src/client/App.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { renderRoutes } from 'react-router-config';
3 | import PropTypes from 'prop-types';
4 | import {Helmet} from './components/';
5 |
6 |
7 | export default class App extends Component {
8 |
9 | static propTypes = {
10 | route: PropTypes.object.isRequired
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
17 |
18 | {/* Set site wide header info here and specific overrides in pages */}
19 | My site title
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {renderRoutes(this.props.route.routes)}
28 |
29 |
30 | );
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/client/App.scss:
--------------------------------------------------------------------------------
1 | .app-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | text-align: center;
6 | margin: 2rem auto;
7 | }
--------------------------------------------------------------------------------
/src/client/codeSplitMappingsAsync.js:
--------------------------------------------------------------------------------
1 | /*
2 | Code Splitting config for Asyncronous load (aka on client)
3 |
4 | This file is paired with codeSplitMappingsSync.js - they must have the same exports.
5 | This file is imported in the client bundle while codeSplitMappingsSync is imported on
6 | the server (see package.json "browser" mappings) - this is necessary for code splitting to work.
7 | The dynamic import(...) calls below are what define the seperate code splitting sub bundles.
8 | */
9 | import { asyncComponent } from 'react-async-component';
10 | import {Loading, ErrorMessage} from './components/';
11 |
12 |
13 | function makeAsyncComponent(importFunc){
14 | return asyncComponent({
15 | resolve: importFunc,
16 | LoadingComponent: Loading, // Optional
17 | ErrorComponent: ErrorMessage // Optional
18 | });
19 | }
20 |
21 | export const HomePage = makeAsyncComponent(() => import('./pages/HomePage/HomePage'));
22 | export const UsersPage = makeAsyncComponent(() => import('./pages/UsersPage/UsersPage'));
23 | export const ErrorPage = makeAsyncComponent(() => import('./pages/ErrorPage/ErrorPage'));
24 |
--------------------------------------------------------------------------------
/src/client/codeSplitMappingsSync.js:
--------------------------------------------------------------------------------
1 | /*
2 | Code Splitting config for Syncronous load (aka on server)
3 |
4 | This file is paired with codeSplitMappingsAsync.js - they must have the same exports.
5 | This file is imported on the server while codeSplitMappingsAsync is imported in the client
6 | bundle (see package.json "browser" mappings) - this is necessary for code splitting to work.
7 | Unlike codeSplitMappingsAsync all imports on server are standard module imports (the server
8 | needs all code imported up front).
9 | */
10 | export {default as HomePage} from './pages/HomePage/HomePage';
11 | export {default as UsersPage} from './pages/UsersPage/UsersPage';
12 | export {default as ErrorPage} from './pages/ErrorPage/ErrorPage';
--------------------------------------------------------------------------------
/src/client/components/common/ErrorMessage/ErrorMessage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 |
5 | export default class ErrorMessage extends Component {
6 |
7 | static propTypes = {
8 | error: PropTypes.object
9 | };
10 |
11 | render() {
12 | const { message = 'Error' } = this.props.error || {};
13 |
14 | return (
15 |