4

Sample repo demonstrating the issue is here.

I'm trying to set up webpack dev server so that:

  1. Requests to / are served by public/landing.html (a static landing page)
  2. Requests to anything else are served by my React app

Using create-react-app, and based on webpack-dev-server's options, I've set up my webpackDevServer.config.js as follows:

historyApiFallback: { // Paths with dots should still use the history fallback. // See https://github.com/facebookincubator/create-react-app/issues/387. disableDotRule: true, rewrites: [ // shows views/landing.html as the landing page { from: /^\/$/, to: 'landing.html' }, // shows views/subpage.html for all routes starting with /subpage { from: /^\/subpage/, to: 'subpage.html' }, // shows views/404.html on all other pages { from: /./, to: '404.html' }, ], },

And when I start webpack here's what I see:

  • Requests to /subpage are routed correctly to subpage.html
  • Requests to /foo are routed correctly to 404.html. Eventually, these would be handled by my React app.
  • Requests to / are routed incorrectly to my React app.

How can I get landing.html to respond to requests at /?

thekevinscott
  • 5,263
  • 10
  • 44
  • 57

2 Answers2

5

I ended up opening a bug request with webpack-dev-middleware and discovered it was not a bug but a failure of configuration.

Specifically, the issue is using HtmlWebpackPlugin alongside historyApiFallback. I believe plugins are processed before the regex matching, and HtmlWebpackPlugin's default file output is index.html; this means that out of the box, / will always be routed to the HtmlWebpackPlugin output file.

The solution to this is to set a custom filename in HtmlWebpackPlugin, which allows you to control the matching again. Here's a sample repo demonstrating the fix and here's the webpack config:

module.exports = {
  context: __dirname,
  entry: [
    './app.js',
  ],
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  devServer: {
    publicPath: "/",
    // contentBase: "./public",
    // hot: true,
    historyApiFallback: {
      // disableDotRule: true,
      rewrites: [
        { from: /^\/$/, to: '/public/index.html' },
        { from: /^\/foo/, to: '/public/foo.html' },
        { from: /(.*)/, to: '/test.html' },
      ],
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Html Webpack html",
      hash: true,
      filename: 'test.html',
      template: 'public/plugin.html',
    }),
  ],
};
Fez Vrasta
  • 14,110
  • 21
  • 98
  • 160
thekevinscott
  • 5,263
  • 10
  • 44
  • 57
1

Two thoughts come to mind:

  1. What you want to do is not possible with Webpack Dev Server (as far as I'm aware)
  2. And, once npm run build is run and deployed the deployed app would not follow the same rules as configured in Webpack Dev Server

Even if I'm mistaken on #1, #2 seems like a bigger issue if you ever plan to deploy the app. This leads me to recommend an alternate setup which will work on dev and production (or wherever it's deployed).

A few options:

  • setup the app as a single-page app (SPA) and use React Router to serve the static routes (/ and /subpage) and wildcards (everything else)
  • setup node (or another server) to serve the static routes (/ and /subpage) and wildcards (everything else)

An Attempt

In an attempt to setup the routes I was able to achieve this setup:

  • Display landing.html when / is requested
  • Display subpage.html when /subpage is requested
  • Display the React App at a specific path, like app.html

To do this make the following changes:

  1. Move /public/index.html to /public/app.html

    mv ./public/index.html ./public/app.html
    
  2. In /config/webpackDevServer.config.js, update historyApiFallback to:

    historyApiFallback: {
      // Paths with dots should still use the history fallback.
      // See https://github.com/facebookincubator/create-react-app/issues/387.
      disableDotRule: true,
      rewrites: [
        // shows views/landing.html as the landing page
        { from: /^\/$/, to: "landing.html" },
        // shows views/subpage.html for all routes starting with /subpage
        { from: /^\/subpage/, to: "subpage.html" },
        // shows views/app.html on all other pages
        // but doesn't work for routes other than app.html
        { from: /./, to: "app.html" }
      ]
    }
    
  3. In /config/paths.js update appHTML to:

    appHtml: resolveApp("public/app.html")
    
  4. In /config/webpack.config.dev.js, updateplugins` to include:

    new HtmlWebpackPlugin({
      filename: "app.html",
      inject: true,
      template: paths.appHtml
    })
    

Requesting a random URL, like localhost:3000/foo will return a blank page but contains the HTML from the app.html page without the bundled <script> tags injected. So maybe you can find a solution to this final hurdle.

Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
  • I appreciate the response! Currently we've got static pages built by one team, and the react app built by another. Eventually we're going to pull the static pages into react, but for now we're just serving them side by side and don't have the bandwidth to refactor them (yet). As far as production, we currently have nginx set up to return `index.html` for `/` routes, and `app.html` as a catch all (where `app.html` is our react app) and we're trying to duplicate that paradigm locally, to no success. – thekevinscott Nov 14 '17 at 01:09
  • What's confusing to me is that the webpack documentation appears to indicate that serving a replacement route at `/^\/$/` is possible (aka, `landing.html`) but when I try and reproduce this locally, it doesn't work. – thekevinscott Nov 14 '17 at 01:13
  • Serving a page other than `index.html` is possible, I was able to get that working. But it only works for specific routes, not as a wildcard. It's close, but the `PUBLIC_URL` variables aren't replaced and the scripts aren't injected. – Brett DeWoody Nov 14 '17 at 01:16
  • For example, I can get `landing.html` to be served at `/`, `subpage.html` at `/subpage` and every other route to use a defined template, like `app.html`, but the scripts aren't injected to run the React app. – Brett DeWoody Nov 14 '17 at 01:18
  • I actually don't need wildcard routing working - just the match against `/`. Mind sharing a repo or the config you're using to get that working? – thekevinscott Nov 14 '17 at 01:23
  • In this setup, the React app is run from a defined path, like ‘app.html’. All other routes could be caught with a static 404 template if needed. On my phone, will share tomorrow. – Brett DeWoody Nov 14 '17 at 01:26
  • Thanks for updating the answer. Unfortunately, with these changes I'm still seeing `app.html` at `/`. `/subpage` produces `subpage.html` and `/foo` produces `app.html` (as you pointed out it would). Maybe this is a bug in webpack? Are you seeing `landing.html` returned? – thekevinscott Nov 14 '17 at 15:42
  • Just to make sure - did you restart the server after making the changes? – Brett DeWoody Nov 14 '17 at 16:33
  • yup, I restarted the server and pushed those changes up to the repo on github. still not seeing the landing page. – thekevinscott Nov 14 '17 at 17:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/158994/discussion-between-brett-dewoody-and-thekevinscott). – Brett DeWoody Nov 14 '17 at 18:46