Compare commits
6 Commits
v2.1.2
...
no-react-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62a9caad1a | ||
|
|
c800477a1b | ||
|
|
b65b1b2a2a | ||
|
|
d0478f2723 | ||
|
|
23cddeae9c | ||
|
|
09e155d625 |
40
README.md
40
README.md
@@ -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).
|
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
|
To deploy a frontend-only React app, use the static-site optimized
|
||||||
▶️ [create-react-app-buildpack](https://github.com/mars/create-react-app-buildpack)
|
▶️ [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
|
## 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
|
* [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)
|
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)
|
* 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)
|
* deployed via `build` script in the Node server's [`./package.json`](package.json)
|
||||||
* module cache configured by `cacheDirectories`
|
* module cache configured by `cacheDirectories`
|
||||||
* bundling via `heroku-postbuild` hook
|
|
||||||
|
|
||||||
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.
|
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)
|
* detect [Node buildpack](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)
|
||||||
* build the app with
|
* build the app with
|
||||||
* `npm install` for the Node server
|
* `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`
|
* launch the web process with `npm start`
|
||||||
* serves `../react-ui/build/` as static files
|
* serves `../react-ui/build/` as static files
|
||||||
* customize by adding API, proxy, or route handlers/redirectors
|
* 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 commit -m 'Migrate from create-react-app-buildpack to Node server'
|
||||||
git push heroku master
|
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
|
## Local Development
|
||||||
|
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "heroku-cra-node",
|
"name": "heroku-cra-node",
|
||||||
"version": "1.0.0",
|
"version": "3.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"array-flatten": {
|
"array-flatten": {
|
||||||
"version": "1.1.1",
|
"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="
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
},
|
},
|
||||||
"media-typer": {
|
"media-typer": {
|
||||||
"version": "0.3.0",
|
"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="
|
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||||
},
|
},
|
||||||
"merge-descriptors": {
|
"merge-descriptors": {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "heroku-cra-node",
|
"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",
|
"description": "How to use create-react-app with a custom Node API on Heroku",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.x"
|
"node": "10.x"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server",
|
"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": [
|
"cacheDirectories": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|||||||
5473
react-ui/package-lock.json
generated
5473
react-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,9 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.6.3",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.8.6",
|
||||||
"react-scripts": "^2.1.2"
|
"react-scripts": "^2.1.8"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
44
react-ui/src/App.js
vendored
44
react-ui/src/App.js
vendored
@@ -1,18 +1,14 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
class App extends Component {
|
function App() {
|
||||||
constructor(props) {
|
const [message, setMessage] = useState(null);
|
||||||
super(props);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
this.state = {
|
const [url, setUrl] = useState('/api');
|
||||||
message: null,
|
|
||||||
fetching: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
const fetchData = useCallback(() => {
|
||||||
fetch('/api')
|
fetch(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`status ${response.status}`);
|
throw new Error(`status ${response.status}`);
|
||||||
@@ -20,19 +16,19 @@ class App extends Component {
|
|||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then(json => {
|
||||||
this.setState({
|
setMessage(json.message);
|
||||||
message: json.message,
|
setIsFetching(false);
|
||||||
fetching: false
|
|
||||||
});
|
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.setState({
|
setMessage(`API call failed: ${e}`);
|
||||||
message: `API call failed: ${e}`,
|
setIsFetching(false);
|
||||||
fetching: false
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
}
|
}, [url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsFetching(true);
|
||||||
|
fetchData();
|
||||||
|
}, [fetchData]);
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
@@ -46,9 +42,9 @@ class App extends Component {
|
|||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
<p>{'« '}<strong>
|
<p>{'« '}<strong>
|
||||||
{this.state.fetching
|
{isFetching
|
||||||
? 'Fetching message from API'
|
? 'Fetching message from API'
|
||||||
: this.state.message}
|
: message}
|
||||||
</strong>{' »'}</p>
|
</strong>{' »'}</p>
|
||||||
<p><a
|
<p><a
|
||||||
className="App-link"
|
className="App-link"
|
||||||
@@ -67,7 +63,7 @@ class App extends Component {
|
|||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
Reference in New Issue
Block a user