Skip to content
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
Closed

Support webpack-dev-server historyApiFallback: true #676

fellz opened this issue Jan 12, 2015 · 42 comments

Comments

@fellz
Copy link

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
Copy link
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
Copy link
Author

fellz commented Jan 12, 2015

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

@gaearon
Copy link
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
Copy link
Author

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
Copy link
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
Copy link
Author

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
Copy link
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
Copy link
Author

fellz commented Jan 12, 2015

@gaearon exactly

@gaearon
Copy link
Contributor

gaearon commented Jan 12, 2015

I'll take a look, thanks for reporting!

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

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

@gaearon
Copy link
Contributor

gaearon commented Jan 15, 2015

I'll take a look now, sorry for delay

@gaearon
Copy link
Contributor

gaearon commented Jan 15, 2015

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

@nelix
Copy link

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
Copy link
Author

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
Copy link
Member

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
Copy link

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
Copy link

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
Copy link

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
Copy link

nelix commented Mar 2, 2015

Thanks @ramnivas

@amacneil
Copy link

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
Copy link

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

@dmwyatt
Copy link

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
Copy link

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

@michaelahlers
Copy link

@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
Copy link

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
Copy link

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

@BenFlanagan
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

@BenFlanagan That was it for me :)

@ccortezia
Copy link

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

@michaelahlers
Copy link

👍

@Dmitry-N-Medvedev
Copy link

@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
Copy link

@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
Copy link

@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
Copy link

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
Copy link

@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
Copy link

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 has been minimized.

@Nigh7Sh4de

This comment has been minimized.

@remix-run remix-run 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.
Labels
None yet
Projects
None yet
Development

No branches or pull requests