After deploying React app to Heroku from dev env, Fetch requests broken - all GET requests returning index.html - others are 404

Viewed 3430

I have just deployed my Create-React-App project to Heroku. In development, I was running two separate ports - the local HTML + JS were being served from the React WebpackDevServer using the npm/yarn start scripts on Port 3000. The Backend was Express + NodeJS running on port 3001. I configured all fetch requests to use mode:'cors' and provided a handler on the API so to avoid CORS errors. A typical fetch request would look like this:

When I deployed to Heroku, everything is now kept together on a single Dyno, and the Express app serves the React files (bundle + index.html) and also handles backend routing logic.

Here is a sample of my API code so-far:

const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const bodyParser = require('body-parser');
const config = require('./models/config');
require('dotenv').config()
const app = express();
const server = require('http').createServer(app);

const storeItems= require('./controllers/storeItems')
const authorize= require('./controllers/authorize')
const router = express.Router(); 

mongoose.Promise = global.Promise; 
mongoose.connect(`mongodb://${process.env.MLABS_USER}:${process.env.MLABS_PW}@ds113000.mlab.com:13000/omninova`, { useMongoClient: true });

app.use(bodyParser.json());

// Middleware to handle CORS in development:

app.use('/*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, x-access-token, x-user-pathway, x-mongo-key, X-Requested-With, Content-Type, Accept");
  res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
  next();
});

app.use(express.static(path.join(__dirname, 'client/build')));

router.route('/api/storeItem/')
    .get(storeItems.getAllStoreItems)
    .post(storeItems.createNewStoreItem);

router.route('/authorize')
    .post(authorize.login);

// Catch-All Handler should send Client index.html for any request that does not match previous routes

router.route('*')
    .get((req, res) => {
        res.sendFile(path.join(__dirname+'/client/build/index.html'));
    });

app.use('/', router);

server.listen(config.port);
module.exports = app;

I'm having some issues, ALL of my get requests are returning my index.html page with the following Error: Unexpected token < in JSON at position 0

I have the following Fetch Request:

    return fetch(`/api/storeItem`, {
        headers:{
            'Content-Type': 'application/json',
        },
        method: 'GET',
        mode: 'no-cors',
    })
    .then(response => response.ok ? response.json() : Promise.reject(response))
    .then(json => {
        dispatch(receiveItems(json))
    })
    .catch(err => console.log(err))

This is failing, because instead of triggering the Express Middleware that should be running storeItems.getAllStoreItems on the backend, It's passing that route and triggering the catch-all handler, which I use to serve the index.html upon initial request:

router.route('*')
    .get((req, res) => {
        res.sendFile(path.join(__dirname+'/client/build/index.html'));
    });

Another confusion is that this following fetch request returns a 404, even though the /authorize route is expecting a POST request in the API code:

export function attemptLogIn(credentials) {
    return dispatch => {
        return fetch('/authorize', {
        headers:{
            'Content-Type': 'application/json'
        },
        method: 'POST',
        mode: 'no-cors'
        body: JSON.stringify(credentials)
        })
        .then(response => response.ok ? response.json() : Promise.reject(response.statusText))
        .then(json => {
            dispatch(routeUserAfterLogin(json.accountType))
        })
        .catch(err => dispatch(authFail(err.message)))
    }
} 

Any help with this would be highly appreciated. I assume I am doing something wrong with the Express Router, since the authorize route is just not being picked up.

I followed the instructions in this blog post to help me set up my new project: https://daveceddia.com/deploy-react-express-app-heroku/

Edit: This is Fetch code from my Development branch. This successfully logs the user in, without returning a 404. However, I do not use the catch-all handler at all, or the express.static middleware:

return fetch('http://localhost:3001/authorize', {
    headers:{
        'Content-Type': 'application/json'
    },
    method: 'POST',
    mode: 'cors', 
    body: JSON.stringify(credentials)
})

Edit: I just changed the URL which points to the bundle.js to app.use(express.static(path.join(__dirname, '/../../build'))); I'm not sure how I was even sending out the HTML before, since that's the actual location of build files..I'm not sure how they were being found before.

Edit2: Found my problem, I left in the start script for my React project (which actually started the webpack dev server...)

3 Answers

For the first part

Error: Unexpected token < in JSON at position 0

My guess is that without the dev server, express can't handle the bundle download automatically. So, when your index.html hits /path/to/bundle.js, it falls under the wildcard ("*") route, which returns the HTML itself. Your browser then tries to parse it as JS, but it can't since it's HTML, hence the error.

I would try something like:

app.get("path/to/bundle.js", (req, res) => {
  res.sendFile("path/to/bundle.js");
});
Related