React Routing works in local machine but not Heroku
Asked Answered
M

5

51

React/react router/heroku question here (it is probably heroku where it is failing).

I am following this wonderful tutorial: https://medium.com/@patriciolpezjuri/using-create-react-app-with-react-router-express-js-8fa658bf892d#.y77yjte2j and everything works up to the point where I post it to heroku and I try to navigate to https://appname.herokuapp.com/about and I get a 404 Not Found/nginx error. Of course, per the tutorial it is supposed to display an About page.

Bottomline: React router is not working on heroku and I can't figure out why.

I have tried modifying my server/app.js file as suggested in this: React routes are not working in facebook's create-react-app build

// server/app.js
const express = require('express');
const morgan = require('morgan');
const path = require('path');

const app = express();

console.log('hi from /src/server.js')
// Setup logger
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));

// Serve static assets
app.use(express.static(path.resolve(__dirname, '..', 'build')));

// Always return the main index.html, so react-router render the route in the client

app.get('/about', (req, res) => {

  console.log('hi from app.get.about')
  console.log(req)
  console.log(res)
  res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
});
app.get('/*', (req, res) => {

  console.log('hi from app.get')
  console.log(req)
  console.log(res)
  res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
});


module.exports = app;

but it doesnt work nor does it log anything at all in the console:

2017-01-20T21:03:47.438140+00:00 heroku[web.1]: Starting process with command `bin/boot`
2017-01-20T21:03:49.540005+00:00 app[web.1]: Injecting runtime env into /app/build/static/js/main.242e967b.js (from .profile.d/inject_react_app_env.sh)
2017-01-20T21:03:49.695317+00:00 app[web.1]: Starting log redirection...
2017-01-20T21:03:49.695899+00:00 app[web.1]: Starting nginx...
2017-01-20T21:03:51.108255+00:00 heroku[web.1]: State changed from starting to up
2017-01-20T21:04:22.720627+00:00 heroku[router]: at=info method=GET path="/" host=sentieoapp1.herokuapp.com request_id=fb8bc13b-f6b5-47bc-8330-443f28e211df fwd="132.147.73.97" dyno=web.1 connect=0ms service=3ms status=200 bytes=627
2017-01-20T21:04:22.746761+00:00 app[web.1]: 10.158.165.5 - - [20/Jan/2017:21:04:22 +0000] "GET / HTTP/1.1" 200 386 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"
2017-01-20T21:04:23.076521+00:00 app[web.1]: 10.158.165.5 - - [20/Jan/2017:21:04:23 +0000] "GET /static/js/main.242e967b.js HTTP/1.1" 200 62263 "https://sentieoapp1.herokuapp.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"
2017-01-20T21:04:23.056416+00:00 heroku[router]: at=info method=GET path="/static/js/main.242e967b.js" host=sentieoapp1.herokuapp.com request_id=436d5ce5-ee39-4ab7-9e12-f5871e0fd552 fwd="132.147.73.97" dyno=web.1 connect=0ms service=25ms status=200 bytes=62540
2017-01-20T21:04:23.745285+00:00 heroku[router]: at=info method=GET path="/static/css/main.9a0fe4f1.css" host=sentieoapp1.herokuapp.com request_id=80438aaa-58c4-456e-8df9-7a29e49bc4ba fwd="132.147.73.97" dyno=web.1 connect=0ms service=2ms status=200 bytes=560
2017-01-20T21:04:23.766676+00:00 app[web.1]: 10.158.165.5 - - [20/Jan/2017:21:04:23 +0000] "GET /static/css/main.9a0fe4f1.css HTTP/1.1" 200 301 "https://sentieoapp1.herokuapp.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"
2017-01-20T21:04:24.044940+00:00 heroku[router]: at=info method=GET path="/static/media/logo.5d5d9eef.svg" host=sentieoapp1.herokuapp.com request_id=bcbc1906-3b90-4f13-a700-f432f79c725d fwd="132.147.73.97" dyno=web.1 connect=0ms service=1ms status=200 bytes=2902
2017-01-20T21:04:24.065013+00:00 app[web.1]: 10.158.165.5 - - [20/Jan/2017:21:04:24 +0000] "GET /static/media/logo.5d5d9eef.svg HTTP/1.1" 200 2671 "https://sentieoapp1.herokuapp.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"
2017-01-20T21:04:26.264631+00:00 heroku[router]: at=info method=GET path="/about" host=sentieoapp1.herokuapp.com request_id=0caef324-9268-4ebb-a3f5-0fb047100893 fwd="132.147.73.97" dyno=web.1 connect=0ms service=4ms status=404 bytes=403
2017-01-20T21:04:26.284717+00:00 app[web.1]: 10.158.165.5 - - [20/Jan/2017:21:04:26 +0000] "GET /about HTTP/1.1" 404 191 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"

and this is where I'm stuck. I am familiar with Express and have gotten it to work on heroku before but this is a whole nother level of nightmare. I understand that this is not server side routing but rather react doing routing from within a single index.html page. But if I can get it to work on my local machine why does it not work on Heroku?

Multiracial answered 20/1, 2017 at 21:12 Comment(1)
Guessing you mean that client-side (dynamic) routing is not working for you. Heroku is expecting server-side (static) routing when you visit a URL, so you must reconfigure the static routes to first load the "index.html" file (which is the `\` endpoint by default). Solutions presented below.Devindevina
E
49

I actually came across this post first before 3 hours of searching through react-router and heroku documentation. For swyx, and anyone else having the same problem, I'll outline the minimum of what you need to do to get this working.

router.js - (Obviously change AppSplash and AppDemo to your components)

export default <Router history={hashHistory}>
  <Route path="/" component={App}>
    <IndexRoute component={AppSplash}/>
    <Route path="demo" component={AppDemo}/>
  </Route>
</Router>

app.js

import React, { Component } from 'react'

class App extends Component {
static propTypes = {
  children: PropTypes.node
}

render() {
  const { children } = this.props
  return (
    <div>
      {children}
    </div>
  )
}
}

export default App

Create a new file in the root of your home directory and name it static.json. Put this into it.

{
  "root": "build/",
  "clean_urls": false,
  "routes": {
    "/**": "index.html"
  }
}

Push to heroku again. The routes should work this time.

Explanation:

You need to modify Heroku's default webpack, otherwise the service gets confused with how to handle the client-side routing. Essentially what static.json does. The rest is just the correct way to handle the routing according to the 'react-router' documentation.

Enantiomorph answered 17/3, 2017 at 20:51 Comment(4)
wow, thanks HarshMarshmallow. this may actually make create-react-app usable for me now that i can put react router on it. I had completely abandoned this route and sought out other fully deployable boilerplates since I asked this question. marking you correct without testingMultiracial
Does that solution work for GitHub pages? I've been blocked for two days... #49046367Fettle
For me this doesn't work, I've opened a new question in #52853377Mettle
Just FYI, this does not work for me at all. Adding static.json has zero effect. dhahn's answer predictably worked, since it makes sure express serves the index file for react-router URL that don't match any express route.Extramural
D
28

How to fix client-side routing errors (Heroku 404 errors):

React Browser Router

If you're using React Browser Router, as an npm module with create-react-app, then the solution (which works for me) is to create a static.json file (within the same directory as package.json).

{
  "root": "build/",
  "clean_urls": false,
  "routes": {
    "/**": "index.html"
  }
}

Here is why this solution works:

Create-react-app is for the most part a Node.Js server which serves client-side React. The public static directory is mapped to the / endpoint, and visiting this endpoint from a browser will download the index.html webpage. This webpage in turn loads the React components. And because React Browser Router is a React component, the routes are loaded dynamically after visiting the / endpoint. In other words, before the index.html webpage is loaded all our React Browser Router routes will result in 404 errors on Heroku. To resolve this issue, a static.json file can be used to map any endpoints with the following pattern /** to the index.html file, which in turn will load React Browser Router and correctly load the react components for that route.

From an Apache HTTP server:

Likewise, on an Apache HTTP server creating an .htaccess file in the public directory, will remap all endpoints that match /** to the index.html file.

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

More resources

Also read the "Deployment" section of the create-react-app README, which has a ton of good information on how to reconfigure the server to use client-side routing.

https://facebook.github.io/create-react-app/docs/deployment

React Static Router

Lastly, React Router offers a static router, React Static Router, which can be used with the "react-dom/server" npm module on a Node.js server, to render JSX server-side, and doesn't need static.json or .htaccess reconfiguration.

Devindevina answered 2/9, 2017 at 12:42 Comment(5)
This solved my problem thank you for the informationDiedra
@tfmontague Does that solution work for GitHub pages? I've been blocked for two days... #49046367Fettle
For IIS/Windows/Azure server, this tut is a good help. medium.com/@to_pe/…Pestiferous
@NullisTrue - facebook.github.io/create-react-app/docs/… - might have a solution?Devindevina
@AnimeshSingh - facebook.github.io/create-react-app/docs/… - also shares a couple other links for automatic deployment to AzureDevindevina
B
4

Try this:

app.get("*", (req, res) => {
  let url = path.join(__dirname, '../client/build', 'index.html');
  if (!url.startsWith('/app/')) // since we're on local windows
    url = url.substring(1);
  res.sendFile(url);
});

Worked for me when I put into server.js.

Brewmaster answered 29/3, 2019 at 18:24 Comment(1)
This worked for me, but only after making sure that I do not return index.html in certain cases, like when requesting transformed.js. I wrote a more detailed explanation as a separate post. Thanks!Circular
C
4

Webpack+Express Solution

TL;DR

Use res.sendFile but don't forget to also return transformed.js and other static files.

Explanations

I've been testing some of the answers on this thread, but none of them really worked for the following setup:

  • I am using webpack (-dev-server) on my local machine.
  • In production (on Heroku), I simply serve the webpack build output with a static express server. Something along the lines of app.use(express.static('${__dirname}/build'));.

This obviously does not work with a react router as static only returns actual files from the build folder (i.e. index.html) and will return 404's on any other url.

David Hahn's proposed solution to use res.sendFile pointed me to the right direction. However, the main issue with hijacking GET * is that the secondary request to transformed.js would also return index.html. After fixing the code to avoid this, I was able to get a working solution.

Code

Here's my server.js:

const express = require("express");

const port = process.env.PORT || 8080;
var app = express();

// List of all the files that should be served as-is
let protected = ['transformed.js', 'main.css', 'favicon.ico']

app.get("*", (req, res) => {

  let path = req.params['0'].substring(1)

  if (protected.includes(path)) {
    // Return the actual file
    res.sendFile(`${__dirname}/build/${path}`);
  } else {
    // Otherwise, redirect to /build/index.html
    res.sendFile(`${__dirname}/build/index.html`);
  }
});

app.listen(port, () => {
  console.log(`Server is up on port ${port}`);
});

Happy to discuss what you think! I am not a React veteran yet so there might be a better way. Cheers!

Circular answered 16/9, 2019 at 4:25 Comment(0)
F
0

I wasted a full day thinking the backend had the problem. After going through all the solutions for the backend and a thorough log inspection, realized it was the react App.js.

I changed each

Axios.post(`${process.env.REACT_APP_BASE_URL}/createOrder`, {

})

to

Axios.post(`/createOrder`, {

})

and it worked!

Fogel answered 19/1, 2022 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.