8 Commits

Author SHA1 Message Date
Mars Hall
450b131035 Remove no-longer-needed install of devDependencies (#36) 2019-07-09 09:17:46 -07:00
Mars Hall
c800477a1b 📚 using Runtime Configuration from create-react-app-buildpack 2019-04-15 15:25:01 -07:00
Mars Hall
b65b1b2a2a Switch to React functional component w/ hooks 2019-04-01 16:20:09 -07:00
Mars Hall
d0478f2723 Upgrade to React 16.8 2019-04-01 15:47:41 -07:00
Mars Hall
23cddeae9c Upgrade to React 16.8 2019-04-01 15:45:43 -07:00
Mars Hall
09e155d625 Switch to Heroku Node.js auto-build 2019-04-01 15:37:45 -07:00
Mars Hall
440b1b2d29 Upgrade to react-dom@16.4.2 to mitigate security issue 2019-01-04 11:48:23 -08:00
Mars Hall
21e4a7bda2 Upgrade to react-scripts@2.1.2 to mitigate security issue 2019-01-04 10:02:54 -08:00
6 changed files with 3820 additions and 2189 deletions

View File

@@ -2,11 +2,16 @@
A minimal example of using a Node backend (server for API, proxy, & routing) with a [React frontend](https://github.com/facebookincubator/create-react-app).
* 📐 [Design Points](#user-content-design-points)
* 🕺 [Demo](#user-content-demo)
* 🚀 [Deploy to Heroku](#user-content-deploy-to-heroku)
* ⤵️ [Switching from create-react-app-buildpack](#user-content-switching-from-create-react-app-buildpack)
* 🎛 [Runtime Config](#user-content-runtime-config)
* 💻 [Local Development](#user-content-local-development)
To deploy a frontend-only React app, use the static-site optimized
▶️ [create-react-app-buildpack](https://github.com/mars/create-react-app-buildpack)
⤵️ [Switching from create-react-app-buildpack](#switching-from-create-react-app-buildpack)?
## Design Points
@@ -16,9 +21,8 @@ A combo of two npm projects, the backend server and the frontend UI. So there ar
* [deployed automatically](https://devcenter.heroku.com/categories/deployment) via heroku/nodejs buildpack
2. [**React UI**](react-ui/): [`react-ui/package.json`](react-ui/package.json)
* generated by [create-react-app](https://github.com/facebookincubator/create-react-app)
* deployed via build hooks in the Node server's [`./package.json`](package.json)
* module cache configured by `cacheDirectories`
* bundling via `heroku-postbuild` hook
* deployed via `build` script in the Node server's [`./package.json`](package.json)
* module cache configured by `cacheDirectories`
Includes a minimal [Node Cluster](https://nodejs.org/docs/latest-v8.x/api/cluster.html) [implementation](server/index.js) to parallelize the single-threaded Node process across the available CPU cores.
@@ -41,7 +45,7 @@ This deployment will automatically:
* detect [Node buildpack](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)
* build the app with
* `npm install` for the Node server
* `heroku-postbuild` for create-react-app
* `npm run build` for create-react-app
* launch the web process with `npm start`
* serves `../react-ui/build/` as static files
* customize by adding API, proxy, or route handlers/redirectors
@@ -79,7 +83,33 @@ If an app was previously deployed with [create-react-app-buildpack](https://gith
git commit -m 'Migrate from create-react-app-buildpack to Node server'
git push heroku master
```
1. If the app uses [Runtime configuration](https://github.com/mars/create-react-app-buildpack/blob/master/README.md#user-content-runtime-configuration), then follow [Runtime config](#user-content-runtime-config) below to keep it working.
## Runtime Config
create-react-app itself supports [configuration with environment variables](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables). These compile-time variables are embedded in the bundle during the build process, and may go stale when the app slug is promoted through a pipeline or otherwise changed without a rebuild. See create-react-app-buildpack's docs for further elaboration of [compile-time vs runtime variables](https://github.com/mars/create-react-app-buildpack/blob/master/README.md#user-content-compile-time-vs-runtime).
[create-react-app-buildpack's runtime config](https://github.com/mars/create-react-app-buildpack/blob/master/README.md#user-content-runtime-configuration) makes it possible to dynamically change variables, no rebuild required. That runtime config technique may be applied to Node.js based apps such as this one.
1. Add the inner buildpack to your app, so that the `heroku/nodejs` buildpack is last:
```bash
heroku buildpacks:add -i 1 https://github.com/mars/create-react-app-inner-buildpack
# Verify that create-react-app-inner-buildpack comes before nodejs
heroku buildpacks
```
2. Set the bundle location for runtime config injection:
```bash
heroku config:set JS_RUNTIME_TARGET_BUNDLE=/app/react-ui/build/static/js/*.js
```
3. Now, build the app with this new setup:
```bash
git commit --allow-empty -m 'Enable runtime config with create-react-app-inner-buildpack'
git push heroku master
```
## Local Development

6
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "heroku-cra-node",
"version": "1.0.0",
"version": "3.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -15,7 +15,7 @@
},
"array-flatten": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
@@ -190,7 +190,7 @@
},
"media-typer": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {

View File

@@ -1,13 +1,13 @@
{
"name": "heroku-cra-node",
"version": "1.0.0",
"version": "3.0.0",
"description": "How to use create-react-app with a custom Node API on Heroku",
"engines": {
"node": "10.x"
},
"scripts": {
"start": "node server",
"heroku-postbuild": "cd react-ui/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
"build": "cd react-ui/ && npm install && npm run build"
},
"cacheDirectories": [
"node_modules",

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "^2.1.8"
},
"scripts": {
"start": "react-scripts start",

108
react-ui/src/App.js vendored
View File

@@ -1,18 +1,14 @@
import React, { Component } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
message: null,
fetching: true
};
}
function App() {
const [message, setMessage] = useState(null);
const [isFetching, setIsFetching] = useState(false);
const [url, setUrl] = useState('/api');
componentDidMount() {
fetch('/api')
const fetchData = useCallback(() => {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`status ${response.status}`);
@@ -20,54 +16,54 @@ class App extends Component {
return response.json();
})
.then(json => {
this.setState({
message: json.message,
fetching: false
});
setMessage(json.message);
setIsFetching(false);
}).catch(e => {
this.setState({
message: `API call failed: ${e}`,
fetching: false
});
setMessage(`API call failed: ${e}`);
setIsFetching(false);
})
}
}, [url]);
useEffect(() => {
setIsFetching(true);
fetchData();
}, [fetchData]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{ process.env.NODE_ENV === 'production' ?
<p>
This is a production build from create-react-app.
</p>
: <p>
Edit <code>src/App.js</code> and save to reload.
</p>
}
<p>{'« '}<strong>
{isFetching
? 'Fetching message from API'
: message}
</strong>{' »'}</p>
<p><a
className="App-link"
href="https://github.com/mars/heroku-cra-node"
>
React + Node deployment on Heroku
</a></p>
<p><a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a></p>
</header>
</div>
);
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{ process.env.NODE_ENV === 'production' ?
<p>
This is a production build from create-react-app.
</p>
: <p>
Edit <code>src/App.js</code> and save to reload.
</p>
}
<p>{'« '}<strong>
{this.state.fetching
? 'Fetching message from API'
: this.state.message}
</strong>{' »'}</p>
<p><a
className="App-link"
href="https://github.com/mars/heroku-cra-node"
>
React + Node deployment on Heroku
</a></p>
<p><a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a></p>
</header>
</div>
);
}
}
export default App;