React-router: Support webpack-dev-server historyApiFallback: true

Created on 12 Jan 2015  路  42Comments  路  Source: ReactTraining/react-router

Hi
I don't know exactly is this issue with webpack but i think so
I have simple config for router

var ReactappApp = require('./ReactappApp');
var HelloWorld = require('./HelloWorld');
var React = require('react');
var Router = require('react-router');
var Route = Router.Route;


var content = document.getElementById('content');

var Routes = (
  <Route path="/" handler={ReactappApp}>
    <Route name="hello" path="hello" handler={HelloWorld}/>
  </Route>
);
console.log(Routes);
Router.run(Routes,Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, content);
});

So i try to load HelloWorld component at _/hello_ and get Cannot GET /hello
So server doesn't see this route

I use grunt and connect also.
Here is grunt task for webpack:

'webpack-dev-server': {
      options: {
        hot: true,
        port: 8000,
        webpack: webpackDevConfig,
        publicPath: '/assets/',
        contentBase: './<%= pkg.src %>/'
    }

and here is webpack config
https://gist.github.com/fellz/b2cc0de81c7b1f5bf442#file-webpack-config

with _historyApiFallback: true_

'webpack-dev-server': {
      options: {
        hot: true,
        port: 8000,
        webpack: webpackDevConfig,
        publicPath: '/assets/',
        contentBase: './<%= pkg.src %>/',
        historyApiFallback: true
    }

it's loading ReactappApp instead of HelloWorld

p.s. react-router-component works as expected and perfectly well

Most helpful comment

I've been struggling with this for longer than I'd like to admit, but I've just figured it out.

Besides enabling historyApiFallback, if you're having trouble _only_ with nested routes, check your html file's reference to your script.

In my case, it was the difference between <script src="/bundle.js"></script> and <script src="bundle.js"></script> causing problems on nested routes. Basic stuff, and super obvious now that I've found it, but it was easy to miss.

Thanks to @adrianmacneil for the sample app, it was a big help.

edit - "/bundle.js" being the correct src, so the reference isn't taken as relative to the current path.

All 42 comments

This is not the router's problem: Webpack just doesn't know you're using pushstate and tries to actually serve /hello from filesystem.

The proper way to do that is to run pushstate server separately and set contentBase to its port.

A hackish but simpler way to do that is to inject a middleware (note this relies on undocumented property and may stop working in future):

    server = new WebpackDevServer(webpack(config), {
      contentBase: contentBase,
      publicPath: config.output.publicPath,
      hot: true
    });

    server.app.use(function pushStateHook(req, res, next) {
      var ext = path.extname(req.url);
      if ((ext === '' || ext === '.html') && req.url !== '/') {
        req.pipe(request(localURL)).pipe(res);
      } else {
        next();
      }
    });

Can't say how to translate this to grunt task tho.

@gaearon
Ok thanks
btw why react-router-components works fine with this stuff ?

I don't know, it shouldn't. If server doesn't serve index.html on /hello, there's nothing any router can do about it.

@gaearon well key diff in this historyApiFallback: true react-router-component works fine with this option and react-router is not ...

I'm not sure what historyApiFallback is (couldn't find it in react-router-component docs) but you can fall back to Router.HashLocation if you'd like.

@gaearon historyApiFallback is an option for webpack-dev-server that you familiar with ) look at the last line

'webpack-dev-server': {
      options: {
        hot: true,
        port: 8000,
        webpack: webpackDevConfig,
        publicPath: '/assets/',
        contentBase: './<%= pkg.src %>/',
        historyApiFallback: true
    }

Ah, thanks. I didn't know dev server supports this. So you're saying it doesn't work with RR but works with RRC?

@gaearon exactly

I'll take a look, thanks for reporting!

@gaearon do you know what the core issue is with historyApiFallback: true?

I'll take a look now, sorry for delay

I couldn't reproduce the problem. In my testing, historyApiFallback works fine with RR.

I find historyApiFallback works with shallow routes but not deep routes, I'm not sure it has anything to do with react-router though.
The reason seems to be requests to my app.js are not resolved to the root like other requests are?
GET http://localhost:8080/orgs/-Jfah70YYREFxUpYaG4C/endpoints/app.js fails?

I found a problem there
have to configure routes properly like this:

var routes = [
    <Route path="/" handler={App} />,
    <Route path="/hello" handler={Hello} />
] 

but still RR doesn't work if you want your routes without hashes (Router.HistoryLocation)

  • historyApiFallback: true flag in webpack-dev-server options must be set

p.s. full project with code is here https://github.com/fellz/react-webpack-app

https://github.com/webpack/react-starter uses pushstate and the router, so something must be up with your app. Let us know if you find what's wrong :)

I just ran into this issue. You have to tell webpack to always serve up index.html. Here is an example, assuming your server.js file and index.html are both in the root of your project:

server.js

var webpack = require('webpack'),
    WebpackDevServer = require('webpack-dev-server'),
    config = require('./webpack.config'),
    path = require("path");

var server = new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
});

// Important part. Send down index.html for all requests
server.use('/', function(req, res) {
  res.sendFile(path.join(__dirname+'/index.html'));
});

server.listen(3010, 'localhost', function (err, result) {
  if (err) {
    console.log(err);
  }

  console.log('Listening at localhost:3010');
});

If you're like me and use html-webpack-plugin this works wonderfully:

var config = require('./webpack.config');
var http = require('http');
var path = require('path');
var url = require('url');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');

var server = new WebpackDevServer(webpack(config), config.devServer);

// Important part. Send down index.html for all requests
server.use('/', function (req, resp, next) {
  var opts = url.parse('http://localhost:8080');
  opts.method = req.method;
  opts.headers = req.headers;

  var myReq = http.request(opts, function (myRes) {
    var statusCode = myRes.statusCode;
    var headers = myRes.headers;
    var location = headers.location;

    if (200 !== statusCode) {
      next();

      return;
    }

    resp.writeHead(myRes.statusCode, myRes.headers);
    myRes.on('error', function (err) {
      next(err);
    });
    myRes.pipe(resp);
  });
  myReq.on('error', function (err) {
    next(err);
  });

  if (!req.readable) {
    myReq.end();
  } else {
    req.pipe(myReq);
  }
});

server.listen(8080, 'localhost', function() {
  console.log('Listening on http://localhost:' + server.get('port'));
});

@nelix I ran into a similar issue. I am not yet using react-router, but the symptom matched to what is being described here. In my case, setting historyApiFallback: true worked when when the root url is loaded first, when I loaded some deep url, browser failed to load a few fonts. https://github.com/webpack/webpack/issues/443 gave me a hint so I tried adding __webpack_public_path__ = "/" in my main entry point. That fixed the deep link reload problem.

Thanks @ramnivas

Some of the examples above are very complicated. In most cases you can get this working simply by adding the following lines to your webpack.config.js file:

devServer: {
  historyApiFallback: true
}

I made an example app to help anyone else looking for a solution to this (like I was).

@adrianmacneil seems that react's about "complexity first" sometimes (:
thanks a lot for pointing this out

@adrianmacneil As mentioned above, that only seems to work for shallow routes. For example, it will work for /foo or /bar but not /foo/bar, unless of course you first visit /foo and then navigate to /foo/bar via a Link.

It works fine for me with nested routes. Try the example app above - I just added a nested route to test it.

@dmwyatt, :+1: on that. I've got a simple case that success for one level deep, but fails beyond that. (Any chance you're lazy-loading files with require.ensure?)

@michaelahlers No, I'm not using require.ensure. However, the example app provided by @adrianmacneil works correctly. I cannot find a difference between what he is doing and what my much more complicated app is doing.

module.exports = {
  entry: './js/main.js',
  output: {
    path: __dirname + '/js',
    filename: 'bundle.js',
    publicPath: '/'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loaders: ['babel-loader']
      }
    ]
  },
  node: {
    net: 'mock',
    dns: 'mock',
    fs: 'empty'
  },
  devtool: 'source-map',
  devServer: {
    historyApiFallback: true
  }
};

With this config my app is now finally loading all routes...but it does a complete page load for each route transition unless I switch to hash routes.

@adrianmacneil's solution worked for me. :-)

I've been struggling with this for longer than I'd like to admit, but I've just figured it out.

Besides enabling historyApiFallback, if you're having trouble _only_ with nested routes, check your html file's reference to your script.

In my case, it was the difference between <script src="/bundle.js"></script> and <script src="bundle.js"></script> causing problems on nested routes. Basic stuff, and super obvious now that I've found it, but it was easy to miss.

Thanks to @adrianmacneil for the sample app, it was a big help.

edit - "/bundle.js" being the correct src, so the reference isn't taken as relative to the current path.

@BenFlanagan thanks a lot. Completely agree with it:

Basic stuff, and super obvious now that I've found it, but it was easy to miss.

@dmwyatt or anybody else, did you find a way to stop the complete page load every time the route changes ?

In addition to @BenFlanagan's excellent observation:

Besides enabling historyApiFallback, if you're having trouble only with nested routes, check your HTML file's reference to your script.

When using the HTML Webpack Plugin to generate your bootstrap page, you'll be able to enforce absolute paths by adding a publicPath to your Webpack configuration:

{
  // ...
  output: {
    // ...
    publicPath: '/',
    // ...
  }
  // ...
}

Incidentally, this should be useful for those encountering issue #113 in react-boilerplate.

@BenFlanagan Adding that single slash fixed a running 2 month bug we had with (only) our nested routes and hot-reloading. Thank you so much!

@BenFlanagan That was it for me :)

@michaelahlers and @BenFlanagan summed up solved the issue here.
Plugins work so entwined that simple problems turn into mazes real quickly.
Thanks !

:+1:

@xuorig, I'm still struggling with full page reload on route navigation. Have you succeeded to solve it? Any ideas how that can be fixed?

@Dmitry-N-Medvedev don't know if it's your case but if you have modified the output.publicPath you should specify the redirect URL.

// output.publicPath: '/foo-app/'
historyApiFallback: {
  index: '/foo-app/'
}

@BenFlanagan solved this problem for me. But then I had issues resolving some of my static resources.

I added <base href="/"> to the head of my index.html and it resolved my issue. This allowed me to leave off the leading / from the build script path. Nested routes correctly resolve and my static resources are loaded.

@dmwyatt did you fix the problem with nested urls? i fixed it with publicPath but now reloads full page instead components :'(

@OscarGalindo

const path = require('path');
const fs = require("fs");

{
  devServer: {
    historyApiFallback: false,
    setup: function (app) {
      app.use(function pushStateHook(req, res, next) {
        var ext = path.extname(req.url);
          if ((ext === '' || ext === '.html') && req.url !== '/') {
            res.setHeader("Content-Type", "text/html");
            fs.createReadStream(helpers.root('dist-dev/index.html')).pipe(res);
          } else {
            next();
          }
        });
      }
  }
}

This issue comes up in google a lot, so this might come in handy to anyone having trouble with this: historyApiFallback will not work well if you are using certain proxy configurations.

I don't know how to fix it other than to remove the proxy option from your dev server configuration.

@sunjay consult http://stackoverflow.com/a/36139331/1615123. Order of proxy and historyApiFallback options in the devServer config matters.

Ran into this issue today. I had to set historyApiFallback: true to get rid of the 404 message. However the Router was still resolving to / instead of the actual route (/redirect in my case). I am working on a react-native project wrapped in react-native-web and I was using the react-router-native router for both web and native. Switching to react-router-dom for web fixed the weird Router behaviour for me in the end. 馃帀

Was this page helpful?
0 / 5 - 0 ratings