Reputation: 2511
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?
Upvotes: 51
Views: 37952
Reputation: 480
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!
Upvotes: 0
Reputation: 1591
Use res.sendFile
but don't forget to also return transformed.js
and other static files.
I've been testing some of the answers on this thread, but none of them really worked for the following setup:
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.
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!
Upvotes: 4
Reputation: 787
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.
Upvotes: 4
Reputation: 17382
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
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.
Upvotes: 28
Reputation: 1037
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.
Upvotes: 49