8
votes

I have...

Back-End

Node.js/express server which serves files when requests are made to certain routes.

Front-End

React pages that makes calls to the back-end requesting data to populate the pages.

I'm using react-router v4 on the front end. Whenever I navigate to a route that is NOT at the exact path AND reload the page, I get a 404 error. I understand why this isn't working; the browser makes a request to a path handled by react-router, and since it doesn't find that route on the server, I get 404.

I'm seeking for a solution to this problem.

// BrowserRouter imported as Router
<Router>
   <Route exact path='/main' component={Main} />
   <Route path='/sub1' component={SubOne} />
   <Route path='/sub2' component={SubTwo} />
</Router>

When I go to /main in the browser, <Main /> is rendered. Say that inside <Main />, there are links to /sub1 and /sub2 respectively. I click on /sub2. Component and page content renders without fail. Then I refresh page, either by accident or intentionally (say component Sub2 lifts state up to Main).

How do I avoid getting 404 after this refresh? How do I get the page/component where "I was" rendered after a refresh if I'm using React-Router?

5
I believe this has to do with your app not being server-side rendered. If you used the create-react-app cli it automatically does this for you. However, if you used your own boilerplate, you need to use webpack to handle 404 fallbacks.Curious13
Correct; I am not using create-react-app, and unfortunately I cannot switch to it at this point. I have to devise a solution for the current setup... How do you setup webpack to handle 404 fallbacks?jsdev17
Make sure you have publicPath: '/' specified in your webpack output.Dennis

5 Answers

4
votes

I had the same issue you're having about 2 months ago. I had very little knowledge about server-side rendering with React. I got confused on the general concept of it. However, I didn't want to use the create-react-app cli. I wanted to use my own boilerplate. Upon doing research, I found out that I had to configure my webpack to handle my 404 reloading fallbacks.

Here is my current webpack setup:

Please note, only pay attention to the historyApiFallback: true that allows you to refresh your page without throwing a 404 error if you're using v4. In addition, i forgot to mention that this requires webpack-dev-server to work.

const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const HtmlWebPackPlugin = require('html-webpack-plugin');

var browserConfig = {
    devServer: {
        historyApiFallback: true,
        proxy: {
            "/api": "http://localhost:3012"
        }
    },
    entry: ['babel-polyfill', __dirname + '/src/index.js'],
    output: {
        path: path.resolve(__dirname + '/public'),
        filename: 'bundle.js',
        publicPath: '/'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    query: {
                        presets: ['react', 'env', 'stage-0']
                    }
                }
            }
        ]
    },
    plugins: [
        new HtmlWebPackPlugin({
            template: './public/index.html',
        })
    ]
}

var serverConfig = {
    target: 'node',
    externals: [nodeExternals()],
    entry: __dirname + '/server/main.js',
    output: {
        path: path.resolve(__dirname + '/public'),
        filename: 'server.js',
        publicPath: '/'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    query: {
                        presets: ['react', 'env', 'stage-0']
                    }
                }
            }
        ]
    }
}

module.exports = [browserConfig, serverConfig]
2
votes

The reason you got 404 is refreshing the page poses network roundtrip to hit the server, while react-router is a client-side routing library, it doesn't handle server-side routing.

when the user hits the refresh button or is directly accessing a page other than the landing page, e.g. /help or /help/online as the web server bypasses the index file to locate the file at this location. As your application is a SPA, the web server will fail trying to retrieve the file and return a 404 - Not Found message to the user.

Since you're using an Express server, connect-history-api-fallback(Express middleware) can be used to proxy all received requests(including unknown ones, /sub2 in your case) through your index page. Then reboot your SPA and route to /sub2 on the client by react router.

If you're using webpack-dev-server for local development, it will be as simple as turning on devServer.historyApiFallback.

2
votes

my solution is using content-history-api-fallback module

first: import history from 'connect-history-api-fallback'

then:

app.use(history({
    rewrites:[
        {from: /^\/api\/.*$/, to: function(context){
            return context.parsedUrl.pathname;
        }},
        {from: /\/.*/, to: '/'}
    ]
}))

app.get('/', function(req, res, next){
    res.render('index');
})

app.use('/api', api);
0
votes

Use the "Redirect" directive to map your sub paths to actual paths

https://reacttraining.com/react-router/web/example/no-match

0
votes

Just add the following "function" to your server application's app.js file:

app.get('*', (req, res) => {
   res.sendFile(path.join(__dirname+'/client/build/index.html'));
});

Replace the "client" with the name of the directory of you client application. This will act as a "catchball" for any requests that don't match the ones that you have defined. It worked for me.

For more info please watch the following tutorial: https://www.youtube.com/watch?v=xwovj4-eM3Y