SimonDegraeve / hapi-webpack-plugin

Webpack middleware for Hapi. Supports HMR.
57 stars 25 forks source link

Custom path with React router returns 404 #11

Closed ioanlucut closed 7 years ago

ioanlucut commented 7 years ago

Hi! First, thanks for the plugin!

The plugin works fine by delivering the index.html at /. However, there's a problem when you are trying to access another route, e.g. /customRoute. This can be solved by adding another custom hook in hapi check if the response status is 404, and deliver the index.html page again.

My question is: is there any official way of handling this situation?

Thanks.

PavelPolyakov commented 7 years ago

Is there any solution for this?

In my regular hapi flow I do this:

/**
     * Trying to catch everything, in order to support vue routing
     */
    server.route({
        method: 'GET',
        path: '/{path*}',
        handler: {
            directory: {
                path: './public',
                listing: false,
                index: true,
                redirectToSlash: true
            }
        }
    });

    server.ext('onPreResponse', (request, reply) => {
        let response = request.response;

        // if 404 - serve Vue app
        if (response.isBoom &&
            response.output.statusCode === 404) {
            return reply.file('./public/index.html');
        }

        reply.continue();
    });

while using webpack dev server it's possible to pass this option:

historyApiFallback: true

While using hapi-webpack-plugin, indeed it returns 404.

@ioanlucut have you found a solution suitable for you?

@SimonDegraeve, @mashaalmemon any hints how this could be accomplished in case of hapi-webpack-plugin usage?

Regards,

PavelPolyakov commented 7 years ago

@ioanlucut

So here is the solution which is working for me:

server.ext('onPreResponse', (request, reply) => {
        let response = request.response;

        // if 404 - serve client app
        if (response.isBoom &&
            response.output.statusCode === 404) {
                return reply.proxy({ uri: `http://${server.info.address}:${server.info.port}/index.html` });
        }
        return reply.continue();
    });

In order to make .proxy work you need https://github.com/hapijs/h2o2 .

updated, the proxy scenario works for both cases, despite the fact if we use webpack or not. It is an analogue of this express module: https://github.com/bripkens/connect-history-api-fallback

mashaalmemon commented 7 years ago

@ioanlucut @PavelPolyakov,

As I understand it, the plugin intercepts all requests and it makes sense then that any request to a non-existent file will return an error.

However conceivably adjustment could be made to allow for such a scenario.

@SimonDegraeve is the maintainer of this repo. Any thoughts @SimonDegraeve?

ioanlucut commented 7 years ago

Many thanks for the solution.

In the meanwhile I managed to make it work like in the following snippet (I am pasting the whole code for instantiating the dev server).

I am using the html-webpack-plugin and I need thwindex.html from compiler memory.

import hapiWebpackPlugin from 'hapi-webpack-plugin';
import bluebird from 'bluebird';
import path from 'path';
import debugFactory from 'debug';
import webpack from 'webpack';
import Hoek from 'hoek';
import config from 'config-loader';
import logger from 'logger';
import HttpStatus from 'http-status-codes';
import webpackConfig from '../config/webpack.config';
import project from '../config/project.config';
import localConfig from '../localConfig';
import serverFactory from './../server';

const debug = debugFactory('app:server');
const APP_NAME = 'app';
// ------------------------------------
// Apply Webpack HMR Middleware
// ------------------------------------
debug('Enabling webpack dev and HMR middleware');
const compiler = webpack(webpackConfig);

const assets = {
  publicPath: webpackConfig.output.publicPath,
  contentBase: project.paths.client(),
  hot: true,
  quiet: project.compiler_quiet,
  noInfo: project.compiler_quiet,
  lazy: false,
  stats: project.compiler_stats,
};
const hot = {
  path: '/__webpack_hmr',
};
const extraPlugins = [{
  register: hapiWebpackPlugin,
  options: { compiler, assets, hot },
}];

config.init(localConfig);
logger.init(config.get('/logger'));

const configureForDevEnvironment = (server) => {
  server.ext('onPreResponse', (request, reply) => {
    const { isBoom, output } = request.response;
    if (isBoom && output.statusCode === HttpStatus.NOT_FOUND) {
      const filename = path.join(compiler.outputPath, 'index.html');
      return bluebird
        .fromCallback(cb => compiler.outputFileSystem.readFile(filename, cb))
        .then(result => reply(result).type('text/html'))
        .catch(err => reply(err).code(HttpStatus.BAD_REQUEST));
    }
    return reply.continue();
  });
};

serverFactory
  .create({
    connectionSettings: {
      port: project.server_port,
    },
    extraPlugins,
  })
  .tap(configureForDevEnvironment)
  .then(server => server.start((startupError) => {
    Hoek.assert(!startupError, startupError);
    debug(`${APP_NAME} running at: ${server.info.uri}`);
  }))
  .catch((error) => {
    debug(`${APP_NAME} failed to start: `, error);
    throw error;
  });
PavelPolyakov commented 7 years ago

@ioanlucut thanks, for some reason the trick with the inmemory filesystem didn't worked for me. I think this solution could be faster then proxy, however proxy looks neat.

ioanlucut commented 7 years ago

@PavelPolyakov Indeed - thank you guys for answer - I think for now these solutions are suitable for others who need this.