React Router with Express, subpages are not routing correctly in production

Viewed 909

I have a React app built with react router and express hosted on Heroku. I can navigate to the homepage ('/') to other pages ('/about') fine, but if I try to navigate directly to a subpage (e.g. '/about') from the browser, Express returns a 500 error.

I tried to always return the index.html page regardless of the request, and then let React Router handle the routing but something is implemented incorrectly. This is only happening in production on Heroku; my local dev environment routing works fine.

Images are also broken on non-root pages.

File Structure:

build
..static
....css
....js
....media
node_modules
public
server
..server.js
src
..config
....switch.js
..imgs
..pages
..scss
..App.js
..index.js
package.json
Procfile
README.md
static.json

server.js:

const path = require('path');
const sslRedirect = require('heroku-ssl-redirect').default;
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.use(sslRedirect());

if (process.env.NODE_ENV === 'production') {
    app.use(express.static('build'));
  app.get('*', (req, res) => {
        res.sendFile('index.html');
  });
} else {
  const publicPath = path.join(__dirname, '..', 'public');app.use(express.static(publicPath));app.listen(port, () => {
    console.log(`Server is up on port ${port}!`);
  });
  app.get('*', (req, res) => {
    res.sendFile(path.join(publicPath, 'index.html'));
  });
}

app.listen(port, () => {
  console.log('Server is up!');
});

App.js:

import React from 'react'
import Header from './header'
import Footer from './footer'
import Switch from './config/switch'
import styled from 'styled-components'

import './App.scss'

const App = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100vh;
`

const AppContent = styled.div`
  width: 1200px;
  margin: 0 auto;
  flex-grow: 99;
  @media (max-width: 1200px) {
    width: 96vw;
  }
`

function AppComponent({history}) {
  return (
    <App>
      <AppContent>
        <Header history={history}/>
        <Switch />
      </AppContent>  
      <Footer />
    </App>

  );
}

export default AppComponent

switch.js:

import React from 'react'
import { Route, Switch, withRouter } from 'react-router-dom'
// Components
import Home from '../pages/home'
import About from '../pages/about'

  render () {
    return (
      <Switch>
        <Route exact path='/' component={Home} />
        <Route exact path='/about' component={About} />
        <Route component={Home} />
      </Switch>
    )
  }
}

export default withRouter(SwitchComponent)
1 Answers

Change your app.get handler in the production environment to

app.get("/*", (req, res) => {
       res.sendFile(path.join(__dirname, "../build", "index.html"), err => {
           if (err) {
               console.log(err);
           }
       });
   });
Related