From 5c7410e900ee57899da02fb1ec54b2d11a550e46 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 13:06:54 -0700 Subject: [PATCH 01/11] Add a link to the gulp module tagger repo. --- docs/guides/logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/logging.md b/docs/guides/logging.md index b7b3b1d74..72b13ac03 100644 --- a/docs/guides/logging.md +++ b/docs/guides/logging.md @@ -63,5 +63,5 @@ var fooLogger = logging.getLogger(__LOGGER__({ label: "foo" })); var barLogger = logging.getLogger(__LOGGER__({ label: "bar" })); ``` -See [react-server-gulp-module-tagger]() for more details on how `__LOGGER__` is +See [react-server-gulp-module-tagger](https://github.com/redfin/react-server/tree/master/packages/react-server-module-tagger) for more details on how `__LOGGER__` is replaced. From 2577f553c0fdf06da8bc6610cf8c00f2631408da Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 13:33:54 -0700 Subject: [PATCH 02/11] Initial take on setting up a guide for use in production. --- docs/_contents.json | 3 +- docs/guides/production.md | 109 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 docs/guides/production.md diff --git a/docs/_contents.json b/docs/_contents.json index 20edabc28..849e13ac0 100644 --- a/docs/_contents.json +++ b/docs/_contents.json @@ -16,7 +16,8 @@ { "name": "Understanding Rendering", "path": "guides/understanding-rendering" }, { "name": "Using ReactServerAgent", "path": "guides/using-react-server-agent"}, { "name": "Using the React Server CLI", "path": "guides/react-server-cli"}, - { "name": "Performing Client Transitions", "path": "guides/client-transitions"} + { "name": "Performing Client Transitions", "path": "guides/client-transitions"}, + { "name": "Running a Production Server", "path": "guides/production"} ] } ] diff --git a/docs/guides/production.md b/docs/guides/production.md new file mode 100644 index 000000000..a0ec3dc6b --- /dev/null +++ b/docs/guides/production.md @@ -0,0 +1,109 @@ +*Work In Progress* + +There are a million ways to run a node.js service in production. This document is broken into multiple sections to +describe the elements that go into running your react-server application in production. + +# Per-Environment Settings + +It helps to setup your project with per-environment settings that are appropriate for your application. In most cases, + things that are helpful in development are not desired in production. A beta environment may be a mix of development + and production settings. To begin, create a `.reactserverrc` file in the root of your project. An example of this + would be + +```json +{ + "routesFile": "./routes.json", + "host": "localhost", + "port": 3000, + "jsPort": 3001, + "hot": false, + "minify": false, + "longTermCaching": true, + "https": false, + "logLevel": "debug", + "env": { + "development": { + "hot": true, + "longTermCaching": false + }, + "beta": { + "jsUrl": "/assets/" + }, + "production": { + "jsUrl": "/assets/", + "logLevel": "error", + "minify": true + } + }, + "webpack-config" : "./webpack.config.js" +} +``` + +Note that the top-level settings are "global" for the entire `react-server` process. The key settings are: `hot`, +`minify`, `jsUrl`, and `longTermCaching`. In specific environments, the settings are changed. For example, when +`NODE_ENV` is set to `development`, then `hot = true` and `longTermCaching = false`. + +To activate any of these settings, make a convenience `npm start` script inside your `package.json` file. The example +below is a snippet of an entire `package.json` file, only showing the relevant script settings. + +```json +{ + "scripts": { + "start": "NODE_ENV=development react-server start", + "start-beta": "NODE_ENV=beta react-server start", + "start-prod": "NODE_ENV=production react-server start" + } +} + ``` + +This way, on a production instance, you can run `npm start-prod` and have all of the appropriate settings. This will +simply start the basic, ExpressJS server included with the `react-server` package. It's not a total solution yet! +Don't worry, we'll get there later in the guide. + +If you want to pass per-environment configuration settings to YOUR application (not to the `react-server` module), +that's easy enough! You can pass an environment variable to `react-server` to tell it where to find these config files. +Following the previous example, you might change the script entries in `package.json` to look like this: + +```json +{ + "scripts": { + "start": "NODE_ENV=development REACT_SERVER_CONFIGS=_configs/development/ react-server start", + "start-beta": "NODE_ENV=beta REACT_SERVER_CONFIGS=_configs/beta/ react-server start", + "start-prod": "NODE_ENV=production REACT_SERVER_CONFIGS=_configs/production/ react-server start" + } +} +``` + +And then create the following directories in the root of your application: + +- `_configs/development/` +- `_configs/beta/` +- `_configs/production/` + +Inside each of those directories, add a file called `config.json` and place any global variables you would like passed +into your application. One example of the file `_configs/production/config.json` might look like this: + +```json +{ + "APP_ENV": "production", + "MY_GLOBAL_VARIABLE": "foo" +} +``` + +To use these config variables inside your application, just use the `config()` function inside `react-server` and +you're all set! This works reliably on both server and client sides of the application--fully isomorphic! +Here's an example: + +```javascript1.6 +import { config } from 'react-server'; +const globalConfig = config(); +const isDevEnvironment = globalConfig.APP_ENV !== "production"; +``` + +# HTTP Server +## `nginx` Fronting Server + +# Static Assets + +# Process Managers/Clustering + From 0cfa6d692f63089b74854c605afc39ef5aa0c776 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 13:53:26 -0700 Subject: [PATCH 03/11] Added more fidelity on ExpressJS --- docs/guides/production.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index a0ec3dc6b..3c7297d17 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -1,6 +1,6 @@ *Work In Progress* -There are a million ways to run a node.js service in production. This document is broken into multiple sections to +There are a million ways to run the `react-server` service in production. This document is broken into multiple sections to describe the elements that go into running your react-server application in production. # Per-Environment Settings @@ -19,7 +19,6 @@ It helps to setup your project with per-environment settings that are appropriat "hot": false, "minify": false, "longTermCaching": true, - "https": false, "logLevel": "debug", "env": { "development": { @@ -101,9 +100,30 @@ const isDevEnvironment = globalConfig.APP_ENV !== "production"; ``` # HTTP Server +Once you move past development, running `react-server` becomes more of a question of how to best serve HTTP/HTTPS requests +[ExpressJS](http://expressjs.com) is the application server of choice in this case. Based on the `.reactserverrc` settings +defined previously in this guide, the ExpressJS server is running on the default port 3000. We likely want to move this +to either 80 or 443 (HTTP or HTTPS) in production to actually serve clients. + +## ExpressJS +Read the ExpressJS guides on running production webservers to gain a better understanding of how all of this works. +Below are some links to get you started: + +- [Security Best Practices](http://expressjs.com/en/advanced/best-practice-security.html) +- [Performance Best Practices](http://expressjs.com/en/advanced/best-practice-performance.html) + +All done? Great. `react-server` should have many of these concepts already in place for your application so you don't +have to think about it. However, since it's your server, you *do* have to think about it! Check out the below links +to see how `react-server` uses/integrates with ExpressJS. + +- [react-server-cli/src/commands/start.js](https://github.com/redfin/react-server/blob/master/packages/react-server-cli/src/commands/start.js) +- [react-server/core/renderMiddleware.js](https://github.com/redfin/react-server/blob/master/packages/react-server/core/renderMiddleware.js) + + ## `nginx` Fronting Server # Static Assets # Process Managers/Clustering +[http://expressjs.com/en/advanced/pm.html](http://expressjs.com/en/advanced/pm.html) From f535cdb41f08b4be3b3320de7259695501ab0362 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 14:11:09 -0700 Subject: [PATCH 04/11] Added nginx fronting server instructions. --- docs/guides/production.md | 54 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 3c7297d17..4298b6cb4 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -119,8 +119,60 @@ to see how `react-server` uses/integrates with ExpressJS. - [react-server-cli/src/commands/start.js](https://github.com/redfin/react-server/blob/master/packages/react-server-cli/src/commands/start.js) - [react-server/core/renderMiddleware.js](https://github.com/redfin/react-server/blob/master/packages/react-server/core/renderMiddleware.js) +If you have everything you want in ExpressJS and you don't need an HTTP/HTTPS server for other things (like PHP, static assets, etc), +then you're finished with this section! If you're wondering how to integrate `react-server` + ExpressJS with an existing +webserver like `nginx`, read on. -## `nginx` Fronting Server +## HTTP/HTTPS Fronting Server +In many cases, you want another web server involved to do other things: serve static assets, render pages in another +language, handle virtual sites, SSL certificates, etc. In that case, you can make your existing server play nice with +`react-server` by using it as a simple [reverse proxy](https://expressjs.com/en/advanced/best-practice-performance.html#proxy). + +### `nginx` +Setting up an `nginx` server from scratch is outside the scope of this document. Please go [here](http://nginx.org/en/docs/) +to get up and running with `nginx` in general. From this point forward, the instructions assume you have `nginx` installed +and properly serving documents on HTTP and/or HTTPS. + +The below example is a complete `server {}` block with everything you need to make a reverse proxy to ExpressJS and serve +static assets from `nginx` for HTTP only. Details are provided after the example + +``` +server { + listen 80; + server_name www.myhostname.com; + + location / { + proxy_pass http://localhost:3000; + + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /assets/ { + alias /path/to/application/__clientTemp/build/; + gzip on; + gzip_types application/javascript text/css; + + expires 30d; + } +} +``` + +- `server_name` defines what hostname to respond to. +- `proxy_pass` tells `nginx` to proxy all HTTP calls to the ExpressJS instance of `react-server`. This setting assumes +that `nginx` and `react-server` are running on the same machine. +- `location /assets/ {}` tells `nginx` to server all assets directly from the `__clientTemp/build` directory that +`react-server` creates when it compiles the application. It also does some compression as well. + +Further enhancements can/should be made on the `nginx` side to account for other needs, but this is a basic working +example. At this point, `nginx` handles all requests on port 80 and proxies it to `react-server` running inside `ExpressJS` +on port 3000. + +### `Apache` # Static Assets From c76993345dc9c22776f3cb6570c63ded8d9afc77 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 14:26:06 -0700 Subject: [PATCH 05/11] Added static asset serving info. --- docs/guides/production.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/guides/production.md b/docs/guides/production.md index 4298b6cb4..58093d8ed 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -173,8 +173,38 @@ example. At this point, `nginx` handles all requests on port 80 and proxies it on port 3000. ### `Apache` +(instructions needed) # Static Assets +This section deals with how to serve static assets alongside `react-server`. Most likely, these should be served outside +of ExpressJS using either an HTTP/HTTPS fronting server or through a CDN. Both methods will be discussed. Additionally, +the two types of static assets that will be discussed in this section are: generated and referenced. + +A generated asset is one that `react-server` "compiles" for you. Your application and any imported assets will be compiled +by `react-server` and loaded automatically. The default configuration is to use the "javascript server" on port 3001 +that is included with `react-server`. Yes, that's right, there are two servers that `react-server` runs: ports 3000 +and 3001. This is probably not what you want in production. + +A referenced asset would be something that your application links to, but `react-server` doesn't know about. This might +be a logo image that you link to with an `` tag. + +## Served through a Fronting Server +The example fronting server configurations show how to configure a fronting HTTP/HTTPS server to serve the static assets +generated by `react-server` after compilation. Simply point the fronting server to the appropriate `__clientTemp/build` +directory and you're all set. + +If you have additional, referenced assets that you want to be served by your fronting server, you can add an addition +`location {}` block to point the server to that directory as well. + +All of this relies on proper configuration of `react-server` as mentioned in the example `.reactserverrc` file at the +beginning of this guide. Because `react-server` loads your compiled assets for you, you need to tell it where to find the assets. +The `jsUrl` setting at the beginning of this guide does just that. If you use `jsUrl: /assets/`, then `react-server` will +try to load all of your assets using the relative URL `/assets`. + +## Served through a CDN +Similar to a fronting server, the key here is to move all of the generated and referenced assets over to a CDN. Once that +is done, you need to modify `.reactserverrc` to set `jsUrl: http://mycdn.com/assets/`. Note that in this case, the URL +is absolute instead of relative. # Process Managers/Clustering From 5374fe2ab5a51e6ab8b1fb903ae91fb1c3d38dce Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 14:41:18 -0700 Subject: [PATCH 06/11] Added one cluster manager config. --- docs/guides/production.md | 45 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 58093d8ed..a473888f5 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -93,7 +93,7 @@ To use these config variables inside your application, just use the `config()` f you're all set! This works reliably on both server and client sides of the application--fully isomorphic! Here's an example: -```javascript1.6 +```javascript es6 import { config } from 'react-server'; const globalConfig = config(); const isDevEnvironment = globalConfig.APP_ENV !== "production"; @@ -206,6 +206,45 @@ Similar to a fronting server, the key here is to move all of the generated and r is done, you need to modify `.reactserverrc` to set `jsUrl: http://mycdn.com/assets/`. Note that in this case, the URL is absolute instead of relative. -# Process Managers/Clustering +# Process & Cluster Managers +Big Flashing Sign: Unhandled Javascript exceptions cause the node.js process to fail! *Do not run `react-server` in production +without some sort of process/cluster manager if your application may throw an unhandled exception or the world will end!* + +Read this first: [http://expressjs.com/en/advanced/pm.html](http://expressjs.com/en/advanced/pm.html) + +## `recluster` +If using [recluster](https://github.com/doxout/recluster) to start `react-server`, add a file called `cluster.js` to the +top level directory of your application and make sure the contents look something like this: + +```javascript es6 +/* eslint-disable no-var */ +let recluster = require('recluster'); +let path = require('path'); +let cluster = recluster(path.join(__dirname, 'node_modules', 'react-server-cli', 'target', 'cli.js'), + {readyWhen: 'ready', workers: 1, args: ['start']}); + +/* eslint-enable no-var */ +cluster.run(); + +process.on('SIGUSR2', function() { + console.log('Got SIGUSR2, reloading cluster...'); + cluster.reload(); +}); +console.log('spawned cluster, kill -s SIGUSR2', process.pid, 'to reload'); +``` + +Add any additional command-line arguments to go to `react-server-cli` in the array of arguments with `args: ['start']`. + +After adding `cluster.js` and ensuring you have `recluster` installed in your `node_modules` directory, edit your `package.json` +file scripts section to look like this: + +```json +{ + "scripts": { + "start": "NODE_ENV=development REACT_SERVER_CONFIGS=_configs/development/ node cluster.js", + "start-beta": "NODE_ENV=beta REACT_SERVER_CONFIGS=_configs/beta/ node cluster.js", + "start-prod": "NODE_ENV=production REACT_SERVER_CONFIGS=_configs/production/ node cluster.js" + } +} +``` -[http://expressjs.com/en/advanced/pm.html](http://expressjs.com/en/advanced/pm.html) From 791dabb9cf0c68400a41352c22a528e4601c3701 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 14:42:36 -0700 Subject: [PATCH 07/11] One final statement. --- docs/guides/production.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guides/production.md b/docs/guides/production.md index a473888f5..14b818dff 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -248,3 +248,6 @@ file scripts section to look like this: } ``` +After all of that, you can run `npm start`, `npm start-beta`, or `npm start-prod` and npm will run `react-server` inside +of `recluster`. + From 39762510b5680b1631cf6e69793da4ddc67f7f4f Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 15:19:00 -0700 Subject: [PATCH 08/11] Added documentation for pm2. --- docs/guides/production.md | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 14b818dff..8e6809312 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -212,6 +212,48 @@ without some sort of process/cluster manager if your application may throw an un Read this first: [http://expressjs.com/en/advanced/pm.html](http://expressjs.com/en/advanced/pm.html) +## `pm2` +If using [pm2](http://pm2.keymetrics.io) to start `react-server`, add a file called `pm2.yml` to the top level directory +of your application. Also, you might want to have the `pm2` module installed globally by running the command `npm install -g pm2`. + +A sample `pm2.yaml` file is included below: + +```yaml +apps: + - script : node_modules/react-server-cli/target/cli.js + args : 'start' + name : 'react-server' + watch : false + env : + NODE_ENV: development + REACT_SERVER_CONFIGS: _configs/development/ + env_beta: + NODE_ENV: beta + REACT_SERVER_CONFIGS: _configs/beta/ + env_production: + NODE_ENV: production + REACT_SERVER_CONFIGS: _configs/production/ +``` + +This example follows the same setup as other parts of this guide by setting and passing the appropriate `NODE_ENV` and +`REACT_SERVER_CONFIGS` variables to `react-server`. Once the module is installed and the `pm2.yml` is in place, you can +start the process using pm2 by running `pm2 start pm2.yml`. + +If you want, you can update the scripts section of `package.json` to interact with pm2 as well: + +```json +{ + "scripts": { + "start": "pm2 start pm2.yml --env development", + "start-beta": "pm2 start pm2.yml --env beta", + "start-prod": "pm2 start pm2.yml --env production" + } +} +``` + +Now you can interact directly with the process using `pm2` commands or use `npm start`, `npm run start-beta`, or +`npm run start-prod` to start the processes. + ## `recluster` If using [recluster](https://github.com/doxout/recluster) to start `react-server`, add a file called `cluster.js` to the top level directory of your application and make sure the contents look something like this: @@ -248,6 +290,6 @@ file scripts section to look like this: } ``` -After all of that, you can run `npm start`, `npm start-beta`, or `npm start-prod` and npm will run `react-server` inside +After all of that, you can run `npm start`, `npm run start-beta`, or `npm run start-prod` and npm will run `react-server` inside of `recluster`. From 5cfe98d563bdca8612e4d6695afee53ba7920720 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Thu, 1 Dec 2016 15:41:08 -0700 Subject: [PATCH 09/11] Add watch = true and ignore_watch settings for pm2 without busting everything. --- docs/guides/production.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 8e6809312..007179f33 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -223,7 +223,8 @@ apps: - script : node_modules/react-server-cli/target/cli.js args : 'start' name : 'react-server' - watch : false + watch : true + ignore_watch: '__clientTemp' env : NODE_ENV: development REACT_SERVER_CONFIGS: _configs/development/ From 1ef994fd98c5c9f9f9257a4cc7d5ddf26451a2da Mon Sep 17 00:00:00 2001 From: PC Drew Date: Fri, 2 Dec 2016 13:52:41 -0700 Subject: [PATCH 10/11] Minor spelling and markdown fixes. --- docs/guides/production.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 007179f33..51de41af9 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -1,5 +1,3 @@ -*Work In Progress* - There are a million ways to run the `react-server` service in production. This document is broken into multiple sections to describe the elements that go into running your react-server application in production. @@ -93,7 +91,7 @@ To use these config variables inside your application, just use the `config()` f you're all set! This works reliably on both server and client sides of the application--fully isomorphic! Here's an example: -```javascript es6 +```javascript import { config } from 'react-server'; const globalConfig = config(); const isDevEnvironment = globalConfig.APP_ENV !== "production"; @@ -172,9 +170,6 @@ Further enhancements can/should be made on the `nginx` side to account for other example. At this point, `nginx` handles all requests on port 80 and proxies it to `react-server` running inside `ExpressJS` on port 3000. -### `Apache` -(instructions needed) - # Static Assets This section deals with how to serve static assets alongside `react-server`. Most likely, these should be served outside of ExpressJS using either an HTTP/HTTPS fronting server or through a CDN. Both methods will be discussed. Additionally, @@ -193,11 +188,11 @@ The example fronting server configurations show how to configure a fronting HTTP generated by `react-server` after compilation. Simply point the fronting server to the appropriate `__clientTemp/build` directory and you're all set. -If you have additional, referenced assets that you want to be served by your fronting server, you can add an addition +If you have referenced assets that you want to be served by your fronting server, you can add an additional `location {}` block to point the server to that directory as well. All of this relies on proper configuration of `react-server` as mentioned in the example `.reactserverrc` file at the -beginning of this guide. Because `react-server` loads your compiled assets for you, you need to tell it where to find the assets. +[beginning of this guide](#per-environment-settings). Because `react-server` loads your compiled assets for you, you need to tell it where to find the assets. The `jsUrl` setting at the beginning of this guide does just that. If you use `jsUrl: /assets/`, then `react-server` will try to load all of your assets using the relative URL `/assets`. @@ -259,7 +254,7 @@ Now you can interact directly with the process using `pm2` commands or use `npm If using [recluster](https://github.com/doxout/recluster) to start `react-server`, add a file called `cluster.js` to the top level directory of your application and make sure the contents look something like this: -```javascript es6 +```javascript /* eslint-disable no-var */ let recluster = require('recluster'); let path = require('path'); From a6690aa9367a30a7ee900ba5549b15c294d7c122 Mon Sep 17 00:00:00 2001 From: PC Drew Date: Sat, 3 Dec 2016 09:00:13 -0700 Subject: [PATCH 11/11] Modify the example `.reactserverrc` to use the correct (new) way of specifying an additional webpack configuration file. --- docs/guides/production.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/production.md b/docs/guides/production.md index 51de41af9..94e9f5566 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -32,7 +32,7 @@ It helps to setup your project with per-environment settings that are appropriat "minify": true } }, - "webpack-config" : "./webpack.config.js" + "webpackConfig" : "./webpack.config.js" } ```