New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support webpack-dev-server historyApiFallback: true #676

Closed
fellz opened this Issue Jan 12, 2015 · 42 comments

Comments

Projects
None yet
@fellz

fellz commented Jan 12, 2015

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

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 12, 2015

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.

@fellz

This comment has been minimized.

fellz commented Jan 12, 2015

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

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 12, 2015

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.

@fellz

This comment has been minimized.

fellz commented Jan 12, 2015

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

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 12, 2015

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.

@fellz

This comment has been minimized.

fellz commented Jan 12, 2015

@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
    }
@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 12, 2015

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

@fellz

This comment has been minimized.

fellz commented Jan 12, 2015

@gaearon exactly

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 12, 2015

I'll take a look, thanks for reporting!

@mjackson mjackson changed the title from Router not working with webpack to Support webpack-dev-server historyApiFallback: true Jan 15, 2015

@mjackson

This comment has been minimized.

Member

mjackson commented Jan 15, 2015

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

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 15, 2015

I'll take a look now, sorry for delay

@gaearon

This comment has been minimized.

Contributor

gaearon commented Jan 15, 2015

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

@nelix

This comment has been minimized.

nelix commented Jan 15, 2015

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?

@fellz

This comment has been minimized.

fellz commented Jan 16, 2015

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

@ryanflorence

This comment has been minimized.

Contributor

ryanflorence commented Feb 1, 2015

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

@marbemac

This comment has been minimized.

marbemac commented Feb 5, 2015

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');
});
@stefanfisk

This comment has been minimized.

stefanfisk commented Feb 11, 2015

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'));
});
@ramnivas

This comment has been minimized.

ramnivas commented Mar 2, 2015

@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. webpack/webpack#443 gave me a hint so I tried adding __webpack_public_path__ = "/" in my main entry point. That fixed the deep link reload problem.

@nelix

This comment has been minimized.

nelix commented Mar 2, 2015

Thanks @ramnivas

@amacneil

This comment has been minimized.

amacneil commented Sep 28, 2015

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

@benbonnet

This comment has been minimized.

benbonnet commented Oct 17, 2015

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

@dmwyatt

This comment has been minimized.

dmwyatt commented Oct 23, 2015

@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.

@amacneil

This comment has been minimized.

amacneil commented Oct 24, 2015

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

@michaelahlers

This comment has been minimized.

michaelahlers commented Oct 25, 2015

@dmwyatt, 👍 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?)

@dmwyatt

This comment has been minimized.

dmwyatt commented Nov 1, 2015

@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.

@diessica

This comment has been minimized.

diessica commented Nov 24, 2015

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

@BenFlanagan

This comment has been minimized.

BenFlanagan commented Nov 28, 2015

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.

@ivstas

This comment has been minimized.

ivstas commented Dec 17, 2015

@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.

@xuorig

This comment has been minimized.

xuorig commented Jan 3, 2016

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

@michaelahlers

This comment has been minimized.

michaelahlers commented Jan 22, 2016

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.

@Ganasist

This comment has been minimized.

Ganasist commented Feb 8, 2016

@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!

@jtribble

This comment has been minimized.

jtribble commented Feb 18, 2016

@BenFlanagan That was it for me :)

@ccortezia

This comment has been minimized.

ccortezia commented Feb 26, 2016

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

@michaelahlers

This comment has been minimized.

michaelahlers commented Feb 28, 2016

👍

@Dmitry-N-Medvedev

This comment has been minimized.

Dmitry-N-Medvedev commented Mar 9, 2016

@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?

@diegoprates

This comment has been minimized.

diegoprates commented Mar 11, 2016

@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/'
}
@lochstar

This comment has been minimized.

lochstar commented Mar 16, 2016

@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.

@OscarGalindo

This comment has been minimized.

OscarGalindo commented Jul 20, 2016

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

@devpreview

This comment has been minimized.

devpreview commented Oct 8, 2016

@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();
          }
        });
      }
  }
}
@sunjay

This comment has been minimized.

sunjay commented Nov 13, 2016

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.

@halfmatthalfcat

This comment was marked as outdated.

halfmatthalfcat commented Feb 5, 2017

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

@Nigh7Sh4de

This comment was marked as resolved.

Nigh7Sh4de commented Jun 6, 2018

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. 🎉

@ReactTraining ReactTraining locked and limited conversation to collaborators Jun 6, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.