Create-react-app: How-to recipes: Using a Node server *and* deploying on heroku

Created on 13 Sep 2016  Â·  26Comments  Â·  Source: facebook/create-react-app

Hey folks,

Thanks for create-react-app! I'm new to the world of React and overall I've had a breeze getting set up. I'm a bit puzzled on deployment still, so I thought I'd open an issue to clarify these instructions in the How-to recipes section as suggested here.

Summary:

  1. I have followed Deployment on Heroku and successfully deployed a backendless React app on Heroku.
  2. I have followed Integrating with a node backend and got it the client and server working together locally.

But I can't get 1 and 2 working together, i.e. a react client + node server working on a single Heroku app.

Github repo that repros the issue:

Questions:

  • I realize that I could deploy 2 separate Heroku apps, one for client and one for server and do the CORS dance to get them to talk to each other. Is that what people usually do for deploying? If so, what's the point of these instructions if you can't actually deploy with that setup?
  • Assuming that there is a way to get a node server and client server working on one Heroku app: Here's my github repo I'm trying to deploy: https://github.com/vrk/food-lookup-demo Can someone take a look at the repro instructions and see if I'm approaching this the wrong way?

Would appreciate any help. Thanks!

proposal

Most helpful comment

Hey all — Thanks for your patience!

I pushed a solution along with a quick write-up to food-lookup-demo. Per my earlier comment, this solution uses the free "one-dyno" solution. We have Node+Express (server.js) serve the static bundle (client/build) alongside the API in production.

I still need to write the blog post that builds off the previous one, describing the steps for readying that repo for deployment. In the meantime, the diff is highly informative. Notably:

  1. We have separate start scripts for production and development (start and run dev, respectively).
  2. We have separate Procfiles for production and development.
  3. We check client/build into git (so we can push it to Heroku).
  4. We have Express serve static assets in production.

Outside of that, I just did some juggling with the PORT environment variable to make everything work OK with Heroku.

Hope this helps!

All 26 comments

Hello, @vrk!
Basically this tutorial is based on the idea, that you open two different ports on the same instance.
So, it's ok when you doing this on your local machine or VPS, but it will not work with _heroku_, where dyno give you possibility expose only one port to outside world and map it to <app_name>.herokuapp.com:80.
So, if you want to use _heroku_ for hosting react app and backend, I guess, you will need two separate apps, so it's Option 1 from your question.

Also there is workaround, you can serve your react app dist directory from node server using express.static, for example, and than the whole app will be served on the same port.

Oh awesome! Thanks for the fast reply. I'll give that express.static thing a try in the next day or two and will report back!

Well that went a lot faster that I thought. Thank you, got it working now! That's just what I needed to know.

For posterity in case someone else comes across this issue, what I did was the following:

  • Add app.use(express.static(path.join(__dirname, 'client/build'))); to my server.js
  • Update the server package.json to include "install": "cd client && npm run build && cd .." under "scripts"
  • (for the food-lookup-demo) Get rid of the foreman stuff (no Procfile or anything)
  • (for the food-lookup-demo) Change process.env.API_PORT to process.env.PORT of course

I'm trying to decide if there's a tutorial I can contribute to the User Guide section of this project , maybe simply an addendum to the Integrating with a Node Backend section that explains the changes needed if someone wants to deploy that to Heroku. I'll keep this open for now for this reason.

@vrk Can you please update your fork of food-lookup-demo to include what you did to get it properly deployed on Heroku? I followed the steps in your fork as well as what you said in this comment and I still can't access the api dyno. I get 500 Internal Server Error.

Never mind, I got it deployed on Heroku using a server that serves the static build.

For anyone else who wants to deploy the original 'create-react-app with a server example' (https://github.com/fullstackreact/food-lookup-demo) to Heroku:

  • Delete Procfile
  • Move "react-scripts": "0.2.3" from _devDependencies_ to _dependencies_
  • In the server's package.json:
  • Replace "start": "nf start -p 3000" with "start": "node server.js"
  • Replace "server": "API_PORT=3001 ./node_modules/.bin/babel-node server.js with "install": "cd client && npm install && npm run build && cd .."
  • In server.js
  • Add const path = require('path');
  • Replace process.env.API_PORT with process.env.PORT
  • Add app.use(express.static(path.join(__dirname, 'client/build')));

Now when you deploy to Heroku, node will start your server and the client dependencies including react-scripts will also be installed before it is run to build your static React app files.

Maybe worth including this into that guide?
cc @acco

@CaliAlec Awesome, glad you got it working!

@gaearon / @acco One thing that wasn't obvious to me until later: the fact that you don't have to/shouldn't run a server for your React apps made through create-react-app, and that the server launched through create-react-app is strictly for debugging.

I think it'd be helpful to mention that more clearly in the following places:

  • npm start: Add a line at the end of the section that's like "Note: In development mode, react-scripts launch a webpack dev server, which is what is running on http://localhost:3000. This server is only used for development and you should not have to run this server for production. When you're ready to deploy, build your app into a single static web page via npm run build instead."
  • npm run build: Add something that says "Your React app can now be served as a static web page via the build/ directory"
  • Integrating with a node backend: Emphasize that these instructions only make sense for development and not deployment: you don't have to have Node backend running on a different port for deployment. To deploy with a node backend, you would follow a pretty different set of instructions, i.e. something that involves "npm run build" and exposing this build directory on your node server.
  • Deployment: I would add one sentence at the top of this section "npm run build" again, like "In general, deploy via 'npm run build'

Feel free to do what you want with those suggestions, including ignore them :) I'm also happy to help with a PR for README.md.

@gaearon Good idea, time has come to add a Heroku/deployments section to the post.

@vrk Thanks for starting this discussion and for all the info! There are two main approaches developers will take here. The first is along the lines that you took — have your node server serve your static assets (the result of npm run build). In that scenario, as you illustrated, you would have to have pretty different setups between development and production.

However, the production deployment we had in mind was a little different. You can use npm run build to generate your static bundle and then toss that onto any static asset server, like S3. Then, you'd deploy your Node/API server to a service like Heroku. example.com would point to index.html on S3 and then the React app would make API requests to something like api.example.comwhich refers to Heroku. This is more of a "pure" separation of client and server and would mean that the setup for production is closer to that of development.

Some hurdles that come to mind with that approach: Handling CORS if you don't have a domain name (using Heroku free tier) and making the dual target deployment strategy palatable. In any case, I'll drop a note here when we've expanded our post to include deployments.

@acco I think a lot of quick (small) projects probably don't bother with a static asset server split from the app server. Especially if you're going to deploy to some fast PaaS setup. Furthermore, handling multiple server processes just makes everything much more complicated if you're going to run things through an end-to-end test or something.

So, I sorta think supporting a single app server that handles client + server is a better developer experience even if in a more disciplined prod deployment, most people would want to get a static app server up and going.

With that in mind, what about making react-scripts expose a middleware that's analogous to react-scripts start?

The method suggested here https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/ (which is linked from the official docs) where you create a nested node.js feels more complicated than necessary. I bet it also would break a lot of auto-detect code in common infrastructure like Travis CI and its default recipes. For example, Travis CI's caching documentation for node.js suggests manually specifying the node_modules directory for cache. With the nested package setup, you'll never cache the client node_modules.

If instead we exported a middleware, similar to how WebpackDevServer exports one in addition to the CLI version, you can just use() it from express.

I imagine a pseudo code looking something like:

const express = require('express');
const reactScripts = require('react-scripts');

app.use('/', reactScripts.createClientRouter());
app.use('/api', myapi_router);

Followed by a modification to package.json so that start no longer directly calls react-scripts start.

This way a fullstack eng could hook into the react-scripts environment programmatically without breaking hermeticity.

Thoughts?

I don’t think we’re at the point where we’re ready to expose any APIs from react-scripts yet.

@gaearon understood about the dangers with that, but:

(a) this is a fairly narrow API since the semantics are just "mount the equiv of react start on this path"
(b) without it, you have a fairly common use case (monolithic server that combines api + asset serving) that requires some moderately complicated workarounds, or ejecting.

I feel like it might be worth considering bumping the priority a bit.

Thanks for all the info and replies!

@acco: Makes sense. Thanks, and looking forward to seeing what you come up with!

(b) without it, you have a fairly common use case (monolithic server that combines api + asset serving) that requires some moderately complicated workarounds, or ejecting.

My drive-by opinion: I agree with @awong-dev. Recommending a deployment solution that would involve 2 Heroku instances (or S3/Heroku combo, or some other pure separation) seems to go slightly against the "simple setup!" spirit of create-react-app. It seems it'd be nice if you could support a way for create-react-app to work harmoniously on a simple monolithic server. (But of course, these things are super easy for me to say as a drive-by observer of this project 😜 )

Hey @awong-dev @vrk —

Thanks for the thoughts!

Totally valid points. I do think it would be great if a developer could avoid working with both S3 and Heroku to deploy a create-react-app app. S3/credentials/s3cmd are all a pain to setup for the first time. There is a Heroku integration that alleviates most of this (Bucketeer) but it's not free.

For a simple, free, one-dyno setup, having your "API" server also serve static assets is the only option (as @vrk highlighted). So it just means there will be some divergence between the setup of your app in development vs production. (You could close this divergence by having your Node+Express server proxy requests to Webpack in development (as opposed to the other way around) but I need to think through if this is more or less confusing.)

In any case, I'll compose examples for each of these scenarios and see what will be reasonable to suggest. Hopefully next week. I'll comment here when I have something

@acco Hi have you come up with anything that works now?

Hi @crazyyi Thanks for pinging me :) Full intention to do this still. I'll post here when I make time for it

Hey all — Thanks for your patience!

I pushed a solution along with a quick write-up to food-lookup-demo. Per my earlier comment, this solution uses the free "one-dyno" solution. We have Node+Express (server.js) serve the static bundle (client/build) alongside the API in production.

I still need to write the blog post that builds off the previous one, describing the steps for readying that repo for deployment. In the meantime, the diff is highly informative. Notably:

  1. We have separate start scripts for production and development (start and run dev, respectively).
  2. We have separate Procfiles for production and development.
  3. We check client/build into git (so we can push it to Heroku).
  4. We have Express serve static assets in production.

Outside of that, I just did some juggling with the PORT environment variable to make everything work OK with Heroku.

Hope this helps!

@acco Is there any reason why you have decided to run the server in production with babel-node? Babel docs recommends against this.

@faurehu There isn't other than to keep things simple :) Indeed, transpiling server.js with babel before deploying would be better for a real production app

Another, IMHO easier way to make this deployable on heroku without buildpack.

  1. heroku create
  2. Make heroku install dev dependenciesheroku config:set NPM_CONFIG_PRODUCTION=false
  3. Add simple dev server npm install express --save + create simple server.js static server, like this one: https://github.com/heroku/node-js-getting-started/blob/master/index.js . Just rename the 'public' to 'build'
  4. Add Procfile for herokuecho 'web: node server.js' > Procfile
  5. Add this postinstall script script to npm scripts: "postinstall" : "npm run build",
  6. git push heroku master

Works like a charm, builds the project on heroku, not locally.

For folks still looking to run create-react-app and express together on heroku, I wrote this article that covers how we are doing it at the moment.
https://originmaster.com/running-create-react-app-and-express-crae-on-heroku-c39a39fe7851

For anyone considering deployment on Heroku with separate instances for server and client and using cookies, keep in mind that herokuapp.com is on the public suffix list meaning that you can't pass cookies directly between two Heroku instances:

https://devcenter.heroku.com/articles/cookies-and-herokuapp-com

A custom domain url bypasses the issue, depending on DNS setup.

Is there anything actionable for us in this issue? Perhaps, somebody would like to add something to the Heroku deployment section in User Guide?

Hello @acco !
After being at this for hours, and looking at several different implementations, your's actually worked! Particularly in removing "/build" from the .gitignore file as per your food-lookup-demo, I'm able to run the build process in my dev environment and just push the entire build folder to Heroku (and not to mention my git repo).

Since it's been about 7 months since you posted that implementation (and things move so fast in the react community :-) ), do you still use this implementation or has your perspective changed on this? Is it still good practice to remove "/build" from the .gitignore in the interest of preserving simplicity when deploying to Heroku?

Let me know.

hey guys, looking through this I'm still struggling a bit to run it the way I need:
1) client + server.js hosted on heroku, which makes request to:
2) a separate api server.js also hosted on heroku

Particularly, I want my client-server to make requests for user data to my api-server on all routes. Is this possible..?

I'll close as I don't see an action item here for us.

GLAH!!!!! BWAHHH! (thats a happy scream) Sorry, I was stuck on my own slightly different problem with this forever!!!

Thank you @vrk and @CaliAlec for this issue.

While slightly different, fixing my scripts to this based off of your responses helped me fix my issue!:

"scripts": {
     "start": "node server.js",
      "client": "cd client && npm run start",
      "server": "nodemon server.js",
      "install": "cd client && npm install && npm run build && cd ..",
      "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\""
  }

Also, I used es6 for the path for a slightly cleaner look. app.use(express.static(${__dirname}/client/build));

Thank you! I hope if there is anyone else with my problem they see this post and see my fix (or yours!)

If anyone is trying to figure out how to do this with a Rails backend, I suggest checking out this blog post: ReactJS + Ruby on Rails API + Heroku App

Seems like the Rails version of this has been neglected. 😞 Maybe a deployment section can also be added to the blog post and readme for others to find.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fson picture fson  Â·  3Comments

rdamian3 picture rdamian3  Â·  3Comments

barcher picture barcher  Â·  3Comments

dualcnhq picture dualcnhq  Â·  3Comments

alleroux picture alleroux  Â·  3Comments