Search code examples
reactjsreact-router-v4hapi.jsreact-router-dom

React routing with Hapi


I have a requirement of using Hapi with create-react-app where Hapi acts as a proxy for api requests and also serves the React app.

I'm trying to get the routing working, but it doesn't seem to work with the current Hapi configuration.

Here is my server code:

const Path = require('path');
const Hapi = require('hapi');
const Inert = require('inert');

const init = async () => {

    const server = new Hapi.Server({
        port: process.env.PORT || 5000,
        routes: {
            files: {
                relativeTo: Path.join(__dirname, '../build')
            }
        }
    });

    await server.register(Inert);

    server.route({
        method: 'GET',
        path: '/{param*}',
        handler: {
            directory: {
                path: '.'
            }
        }
    });

    const options = {
        ops: {
            interval: 1000
        },
        reporters: {
            myConsoleReporter: [
                {
                    module: 'good-console',
                    args: [{ request: '*', response: '*' }]
                },
                'stdout'
            ]
        }
    };

    await server.register({
        plugin: require('good'),
        options,
    });

    await server.start();

    console.log('Server running at:', server.info.uri);
};

init();

The index.html file loads fine when localhost:5000 is open. I have configured a route /dashboard in the react-router part. Hitting localhost:5000/dashboard gives a 404.

Questions:

  • How do I configure the routes in Hapi so that React takes over the routing after the index.html is rendered?
  • The current server code serves the app from the build folder after the app is built. How do I configure it for hot reload without ejecting from the create-react-app

Note: The routing works when running the react app with npm start. But this is without the Hapi server running.

I'm new to using Hapi so any pointers are appreciated.


Solution

  • So I played around with various hapi+inert combo and this is what ended up working for me.

    server.js

    const Path = require('path');
    const Hapi = require('hapi');
    const Inert = require('inert');
    const routes = require('./routes');
    const init = async () => {
    
        console.log('Routes are', routes);
        const server = new Hapi.Server({
            port: process.env.PORT || 5000,
            routes: {
                files: {
                    relativeTo: Path.join(__dirname, '../build')
                }
            }
        });
    
        await server.register(Inert);
    
        server.route(routes);
    
        /**
         * This is required here because there are references to *.js and *.css in index.html,
         * which will not be resolved if we don't match all remaining paths.
         * To test it out, comment the code below and try hitting /login.
         * Now that you know it doesn't work without the piece of code below,
         * uncomment it.
         */
        server.route({
            method: 'GET',
            path: '/{path*}',
            handler: {
                directory: {
                    path: '.',
                    redirectToSlash: true,
                    index: true,
                }
            }
        });
    
        const options = {
            ops: {
                interval: 1000
            },
            reporters: {
                myConsoleReporter: [
                    {
                        module: 'good-console',
                        args: [{ request: '*', response: '*' }]
                    },
                    'stdout'
                ]
            }
        };
    
        await server.register({
            plugin: require('good'),
            options,
        });
    
        await server.start();
    
        console.log('Server running at:', server.info.uri);
    };
    
    init();
    

    /routes/index.js

    /**
    * Use this for all paths since we just need to resolve index.html for any given path.
    * react-router will take over and show the relevant component.
    * 
    * TODO: add a 404 handler for paths not defined in react-router
    */
    const fileHandler = {
        handler: (req, res) => {
            console.log(res.file('index.html'));
            return res.file('index.html');
        }
    }
    
    const routes = [
        { method: 'GET', path: '/login', config: fileHandler },
    ]
    
    module.exports = routes;
    

    The key thing to observe here is that for any named path (in this case /login) we always return the index.html file. For all other paths we tell hapi to return files from out of our build directory so that any references to *.css or *.js file in our index.hml will be resolved and we don't face a 404.

    I'm not sure how react-router takes over the path resolution once index.html is loaded, but it beyond the scope of this question and is a topic of discussion for another time maybe.

    As for the second question regarding hot-reload, I am still trying to figure it out. For now I am running both the hapi server and react-app independently, as I need /api for use in the react-app. Any suggestions or answers are welcome.