1

I am new to Heroku and deployment. I created a project using create-react-app and using redux as framework. I am having an issue with react router when deploying to Heroku. When i click on the links in my app, the router works, but when I refresh the page, it throws 404 Not Found error.

This is my index.js

import { Router, Route, browserHistory, IndexRoute } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import { Provider } from 'react-redux'
import ReactDOM from 'react-dom'
import React from 'react'

import App from './containers/App'
import configure from './store'
import Dashboard from './components/v1/dashboard/dashboard';
import Login from './components/v1/login/login';

const store = configure();
const history = syncHistoryWithStore(browserHistory, store);

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={Login} />
        <Router path="/dashboard" component={Dashboard}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

this this my webpack.config.prod.js

'use strict';

var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ManifestPlugin = require('webpack-manifest-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var paths = require('./paths');
var getClientEnvironment = require('./env');


var publicPath = paths.servedPath;
var shouldUseRelativeAssetPaths = publicPath === './';
var publicUrl = publicPath.slice(0, -1);
var env = getClientEnvironment(publicUrl);

if (env.stringified['process.env'].NODE_ENV !== '"production"') {
  throw new Error('Production builds must have NODE_ENV=production.');
}

const cssFilename = 'static/css/[name].[contenthash:8].css';
const extractTextPluginOptions = shouldUseRelativeAssetPaths
  ? { publicPath: Array(cssFilename.split('/').length).join('../') }
  : undefined;

module.exports = {
  bail: true,
  devtool: 'source-map',
  entry: [
    require.resolve('./polyfills'),
    paths.appIndexJs
  ],
  output: {
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: publicPath
  },
  resolve: {
    fallback: paths.nodePaths,
    extensions: ['.js', '.json', '.jsx', ''],
    alias: {
      'react-native': 'react-native-web'
    }
  },

  module: {
    preLoaders: [
      {
        test: /\.(js|jsx)$/,
        loader: 'eslint',
        include: paths.appSrc
      }
    ],
    loaders: [
      {
        exclude: [
          /\.html$/,
          /\.(js|jsx)$/,
          /\.css$/,
          /\.scss$/,
          /\.json$/,
          /\.(jpe?g|png|gif|svg)$/i
        ],
        loader: 'url',
        query: {
          limit: 10000,
          name: 'static/media/[name].[hash:8].[ext]'
        }
      },
      {
        test: /\.(js|jsx)$/,
        include: paths.appSrc,
        loader: 'babel',

      },
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract(
          'style',
          'css?importLoaders=1&modules&localIdentName=[name]__[local]___[hash:base64:5]!postcss',
          extractTextPluginOptions
        )
      },
      {
        test: /\.scss$/,
        loaders: ["style", "css", "sass"]
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loader: 'file',
        query: {
          name: 'static/media/[name].[hash:8].[ext]'
        }
      }
    ]
  },

  postcss: function() {
    return [
      autoprefixer({
        browsers: [
          '>1%',
          'last 4 versions',
          'Firefox ESR',
          'not ie < 9', // React doesn't support IE8 anyway
        ]
      }),
    ];
  },
  plugins: [
    new InterpolateHtmlPlugin(env.raw),
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      }
    }),
    new webpack.DefinePlugin(env.stringified),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true, // React doesn't support IE8
        warnings: false
      },
      mangle: {
        screw_ie8: true
      },
      output: {
        comments: false,
        screw_ie8: true
      }
    }),
    new ExtractTextPlugin(cssFilename),
    new ManifestPlugin({
      fileName: 'asset-manifest.json'
    })
  ],
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  }
};

And this is my package.json

{
  "name": "olep2",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "draft-js": "^0.10.0",
    "draft-js-export-html": "^0.5.2",
    "es6-promise": "^4.0.5",
    "express": "^4.15.2",
    "jquery": "^3.1.1",
    "lodash": "^4.17.4",
    "moment": "^2.17.1",
    "react": "^15.5.4",
    "react-bootstrap": "^0.30.7",
    "react-day-picker": "^5.0.0",
    "react-dom": "^15.5.4",
    "react-fontawesome": "^1.5.0",
    "redux-thunk": "^2.2.0",
    "requirejs": "^2.3.2"
  },
  "devDependencies": {
    "classnames": "^2.2.3",
    "cross-env": "^4.0.0",
    "css-loader": "0.26.1",
    "es6-promise": "^4.0.5",
    "file-loader": "0.10.0",
    "image-webpack-loader": "^3.2.0",
    "node-sass": "^4.5.0",
    "npm-run-all": "^4.0.2",
    "postcss-loader": "1.2.2",
    "prop-types": "^15.5.4",
    "react-hot-loader": "^1.3.0",
    "react-redux": "^4.4.0",
    "react-router": "^2.0.0",
    "react-router-redux": "^4.0.0",
    "redux": "^3.3.1",
    "redux-actions": "^0.9.1",
    "rucksack-css": "^0.8.5",
    "sass-loader": "^6.0.2",
    "style-loader": "0.13.1",
    "autoprefixer": "6.7.2",
    "babel-core": "6.22.1",
    "babel-eslint": "7.1.1",
    "babel-jest": "18.0.0",
    "babel-loader": "6.2.10",
    "babel-preset-react-app": "^2.2.0",
    "babel-runtime": "^6.20.0",
    "case-sensitive-paths-webpack-plugin": "1.1.4",
    "chalk": "1.1.3",
    "connect-history-api-fallback": "1.3.0",
    "cross-spawn": "4.0.2",
    "detect-port": "1.1.0",
    "dotenv": "2.0.0",
    "eslint": "3.16.1",
    "eslint-config-react-app": "^0.6.2",
    "eslint-loader": "1.6.0",
    "eslint-plugin-flowtype": "2.21.0",
    "eslint-plugin-import": "2.0.1",
    "eslint-plugin-jsx-a11y": "4.0.0",
    "eslint-plugin-react": "6.4.1",
    "extract-text-webpack-plugin": "1.0.1",
    "fs-extra": "0.30.0",
    "html-webpack-plugin": "2.24.0",
    "http-proxy-middleware": "0.17.3",
    "jest": "18.1.0",
    "json-loader": "0.5.4",
    "object-assign": "4.1.1",
    "promise": "7.1.1",
    "react-dev-utils": "^0.5.2",
    "url-loader": "0.5.7",
    "webpack": "1.14.0",
    "webpack-dev-server": "1.16.2",
    "webpack-manifest-plugin": "1.1.0",
    "whatwg-fetch": "2.0.2"
  },
  "scripts": {
    "start": "node scripts/start.js ",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx}"
    ],
    "setupFiles": [
      "<rootDir>/config/polyfills.js"
    ],
    "testPathIgnorePatterns": [
      "<rootDir>[/\\\\](build|docs|node_modules|scripts)[/\\\\]"
    ],
    "testEnvironment": "node",
    "testURL": "http://localhost",
    "transform": {
      "^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
      "^(?!.*\\.(js|jsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
    },
    "transformIgnorePatterns": [
      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"
    ],
    "moduleNameMapper": {
      "^react-native$": "react-native-web"
    }
  },
  "babel": {
    "presets": [
      "react-app"
    ]
  },
  "eslintConfig": {
    "extends": "react-app"
  }
}

4 Answers 4

5

The problem is that your webserver is serving "index.html" only for the root route ("/")

You will have to configure the webserver in a way that all the routes (or only the ones that you are using) will serve index.hml

Read that for more information Heroku Buildpack for create-react-app: static hosting for React.js web apps

Sign up to request clarification or add additional context in comments.

1 Comment

I did use it to to first create heroku, but I then had to eject in order to be able to config css modules. So will that mean that I have to create a server.js with express? or is there a way around it?
1

Try this code:

Server.js:

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

Worked for me.

1 Comment

While this code snippet may solve the question, including an explanation helps to improve the quality of your response. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
0

Please replace Router with HashRouter. HashRouter is a part of react-router-dom, not comes from core react-router package.

import { HashRouter } from 'react-router-dom';

<HashRouter>
  …
</HashRouter>

Comments

0

replace createBrowserRouter to createHashRouter into App.js helped me

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.