historyApiFallback doesn't work in Webpack dev server
Asked Answered
V

9

37

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/'
},
Vibration answered 17/5, 2016 at 8:37 Comment(0)
L
45

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.

Leopoldine answered 5/7, 2016 at 15:38 Comment(3)
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 : '/' }Vanessa
@BijayRai I have the same config as yours. How did you solve the problem?Archery
其他的配置省略 translated using google translate is "Other configuration omitted".Prod
B
22
output: {
    ...
    publicPath: "/"
  },

Adding public path solved this for me

Busiek answered 27/11, 2018 at 11:56 Comment(0)
N
18

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

        historyApiFallback: {
            index: '/'
        }
Namtar answered 16/4, 2019 at 8:22 Comment(2)
for anyone else using React + latest webpack, this was the only solution which helped listed in thread.Arabist
Using webpack 5, this is the only solution that worked.Canister
C
10

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. :)

Cosimo answered 7/5, 2021 at 1:59 Comment(1)
This output.publicPath: '/' saves my day. Thanks!Corrasion
C
1

If you're finding that some paths work but others don't, you may be falling foul of the "dot rule". Per the docs:

When using dots in your path (common with Angular), you may need to use the disableDotRule:

webpack.config.js

module.exports = {
  //...
  devServer: {
    historyApiFallback: {
      disableDotRule: true,
    },
  },
};
Cree answered 7/3, 2022 at 15:40 Comment(0)
S
0

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
    },
...
}
Shaunta answered 21/5, 2018 at 7:9 Comment(1)
They're using this already by passing in the object where they were trying to use a specific indexJohnstone
I
0

For me this setting worked out:

 module.exports = {
    output: {
        path: path.resolve(__dirname, 'build'),
        publicPath: '/',
        filename: 'bundle-main.js'
    },

    devServer: {
    /* Telling the server to fallback to index.html if the route is not found at the backend server */
      historyApiFallback: true,
    }
};
Inertia answered 6/6, 2022 at 3:20 Comment(0)
R
0

In webpack.config.js just add these lines

devServer: {
    open: true,
    historyApiFallback: true,
   allowedHosts: 'all',
   hot: true
    
  },

and add this -> // prefixes: ["http://your url:8081/"] it's working perfectly.

Roemer answered 29/7, 2022 at 7:29 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Speciation
B
0

This code work for me. i get 404.html

devServer: {
    static: {
      directory: "./dist",
    },
    historyApiFallback: {
      rewrites: [
        { from: /^\/$/, to: './index.html' },
        { from: /./, to: './404.html' }
      ]
    }
  },
Basrelief answered 28/4, 2023 at 3:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.