29

I use Webpack dev server and browserHistory in React Router to manipulate with urls by HTML5 History API. historyapifallback-option does not work in my webpack config file. After refreshing http://localhost:8080/users or http://localhost:8080/products I got 404.

webpack.config.js

var webpack = require('webpack');
var merge = require('webpack-merge');

const TARGET = process.env.npm_lifecycle_event;

var common = {
    cache: true,
    debug: true,
    entry: './src/script/index.jsx',
    resolve: {
        extensions: ['', '.js', '.jsx']
    },
    output: {
        sourceMapFilename: '[file].map'
    },
    module: {
        loaders: [
            {
                test: /\.js[x]?$/,
                loader: 'babel-loader',
                exclude: /(node_modules)/
            }
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        })
    ]
};

if(TARGET === 'dev' || !TARGET) {
    module.exports = merge(common,{
        devtool: 'eval-source-map',
        devServer: {
            historyApiFallback: true
        },
        output: {
            filename: 'index.js',
            publicPath: 'http://localhost:8090/assets/'
        },
        plugins: [
            new webpack.DefinePlugin({
                'process.env.NODE_ENV': JSON.stringify('dev')
            })
        ]
    });
}

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
        <title>Test</title>
    </head>
    <body>
        <div id="content">
            <!-- this is where the root react component will get rendered -->
        </div>
        <script src="http://localhost:8090/webpack-dev-server.js"></script>
        <script type="text/javascript" src="http://localhost:8090/assets/index.js"></script>
    </body>
</html>

index.jsx

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, useRouterHistory, browserHistory, Link} from 'react-router';

class Home extends Component{
  constructor(props) {
    super(props);
  }

  render() {
      return <div>
          I am home component
          <Link to="/users" activeClassName="active">Users</Link>
          <Link to="/products" activeClassName="active">Products</Link>
        </div>;
  }
}

class Users extends Component{
  constructor(props) {
    super(props);
  }

  render() {
      return <div> I am Users component </div>;
  }
}

class Products extends Component{
  constructor(props) {
    super(props);
  }

  render() {
      return <div> I am Products component </div>;
  }
}

ReactDOM.render(
    <Router history={browserHistory} onUpdate={() => window.scrollTo(0, 0)}>
        <Route path="/" component={Home}/>
        <Route path="/users" component={Users} type="users"/>
        <Route path="/products" component={Products} type="products"/>
    </Router>
    , document.getElementById('content'));

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.jsx",
  "scripts": {
    "start": "npm run serve | npm run dev",
    "serve": "./node_modules/.bin/http-server -p 8080",
    "dev": "webpack-dev-server -d --progress --colors --port 8090 --history-api-fallback"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "events": "^1.1.0",
    "jquery": "^2.2.3",
    "path": "^0.12.7",
    "react": "^15.0.2",
    "react-dom": "^15.0.2",
    "react-mixin": "^3.0.5",
    "react-router": "^2.4.0"
  },
  "devDependencies": {
    "babel": "^6.5.2",
    "babel-core": "^6.8.0",
    "babel-loader": "^6.2.4",
    "babel-polyfill": "^6.8.0",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "babel-register": "^6.8.0",
    "http-server": "^0.9.0",
    "webpack": "^1.13.0",
    "webpack-dev-server": "^1.14.1",
    "webpack-merge": "^0.12.0"
  }
}

I tried to change devServer in my config, but it didn't help:

devServer: {
    historyApiFallback: {
        index: 'index.html',
    }
},

devServer: {
    historyApiFallback: {
        index: 'index.js',
    }
},

devServer: {
    historyApiFallback: {
        index: 'http://localhost:8090/assets',
    }
},

devServer: {
    historyApiFallback: {
        index: 'http://localhost:8090/assets/',
    }
},

devServer: {
    historyApiFallback: {
        index: 'http://localhost:8090/assets/index.html',
    }
},

devServer: {
    historyApiFallback: {
        index: 'http://localhost:8090/assets/index.js',
    }
},

devServer: {
    historyApiFallback: {
        index: 'http://localhost:8090/assets/index.js',
    }
},
output: {
    filename: 'index.js',
            publicPath: 'http://localhost:8090/assets/'
},
Matt
  • 6,793
  • 24
  • 89
  • 169

5 Answers5

39

I meet the same question today. let config in webpack.config.js: output.publicPath be equal to devServer.historyApiFallback.index and point out html file route。my webpack-dev-server version is 1.10.1 and work well. http://webpack.github.io/docs/webpack-dev-server.html#the-historyapifallback-option doesn't work, you must point out html file route.

for example

module.exports = {
    entry: "./src/app/index.js",
    output: {
        path: path.resolve(__dirname, 'build'),
        publicPath: 'build',
        filename: 'bundle-main.js'
    },
    devServer: {
        historyApiFallback:{
            index:'build/index.html'
        },
    },
};

historyApiFallback.index indicate that when url path not match a true file,webpack-dev-server use the file config in historyApiFallback.index to show in browser rather than 404 page. then all things about your route change let your js using react-router do it.

Laxmikant Dange
  • 6,983
  • 4
  • 36
  • 60
echizen
  • 426
  • 4
  • 4
  • 1
    output: { publicPath: '/', path: path.join(__dirname, 'public'), filename: 'bundle.js' }, devServer: { historyApiFallback: true, } In my case , my output path is like this and here historyApi fallback is not working even if i placed historyApiFallback : { index : '/' } – Bijay Rai Apr 04 '17 at 10:08
  • @BijayRai I have the same config as yours. How did you solve the problem? – Anvesh Checka Aug 31 '17 at 04:19
  • 其他的配置省略 translated using google translate is "Other configuration omitted". – Millar248 Jan 09 '19 at 14:58
15
output: {
    ...
    publicPath: "/"
  },

Adding public path solved this for me

amit
  • 1,815
  • 1
  • 16
  • 26
14

I had this problem and was only able to fix it using index: '/' with webpack 4.20.2

        historyApiFallback: {
            index: '/'
        }
jsdeveloper
  • 3,416
  • 1
  • 11
  • 14
  • for anyone else using React + latest webpack, this was the only solution which helped listed in thread. – rob2d Sep 03 '19 at 19:26
1

Ref.: https://webpack.js.org/configuration/dev-server/#devserver-historyapifallback

This works with any react-router

You have to add historyApiFallback: true

module.exports = {
    cache: true,
    entry: "./index.js",
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'public')
    },
    context: SRC,
    devServer: {
        contentBase: path.resolve(__dirname, 'public/assets'),
        stats: 'errors-only',
        open: true,
        port: 8080,
        compress: true,
        historyApiFallback: true
    },
...
}
STEEL
  • 5,589
  • 8
  • 52
  • 73
  • They're using this already by passing in the object where they were trying to use a specific `index` – toastal Nov 12 '19 at 06:09
0

There is a very tricky thing going on here!

The 404 can be two totally different things below. You can open the Network tab of Chrome to see if it's the initial request that is 404, or the assets within.

If you are not afraid of terminal, you can also do curl -v http://localhost:8081/product to see the actual HTTP response code.

Case 1: 404 on the initial HTML page

This is the case that set-up with historyFallbackApi is not correct.

Usually just historyApiFallback: true should work in devServer configs of Webpack. (source) Yet, if you happen to have a custom output folder, you have to set the index property of historyApiFallback to the same path, as advised by the other answers on this question like jsdeveloper's or echzin's.

Case 2: 404 on the assets (e.g. bundle.js or vendor.js)

This one is quite interesting!

In this case you do actually get the initial HTML (i.e. if you add view-source: before your URL to become view-source:http://localhost:8081/admin, you see your HTML, and/or curl command shows the HTML), but the page doesn't work in the browser.

What historyApiFallback does, which might sound like a magic, is literally just setting req.url of the Express server to be the index.html file, for any incoming GET request within the domain. (The main two lines in the Source Code)

However, if the path to your assets is relative (e.g. in the view-source, you see <script src="vendor.js"></script>) AND the path that you are landing is not at the same path-depth as index (e.g. you are trying to load http://localhost:8081/admin/user/12/summary while the main server is at http://localhost:8081/admin), what happens is it cannot find the .js files for your JavaScript code. (in my case http://localhost:8081/admin/user/12/vendor.js)

enter image description here

Note that whatever router that deals with HTML5 History here (react router or vue router), knows how to initialize the internal path to document.location.href upon the initial load. But, it doesn't know where is the "root" to properly update the assets' path to it. (And maybe it's not even its job, in terms of responsibility.) As a result, the path to assets would be calculated based on the URL's path, not the index.html's path! So, for just src="vendor.js" with no absolute / prefix, it tries to find /admin/user/12/vendor.js instead of /vendor.js.

What you need to do here is to make sure the output path of your WebPack is an absolute path and starts with /, so regardless of the landing URL, it would always work. i.e. it's always <script src="/vendor.js"></script> in the HTML source.

To do so, you have to set output.publicPath to be an absolute path (with or without domain). You can verify this via the view-source: or curl technique above. :)

Aidin
  • 7,505
  • 2
  • 35
  • 33