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?
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?
Share Improve this question edited May 11, 2018 at 3:57 Allen 4,7793 gold badges29 silver badges43 bronze badges asked May 11, 2018 at 0:02 jsdev17jsdev17 1,1103 gold badges16 silver badges25 bronze badges 3 |5 Answers
Reset to default 5I 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]
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.
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);
Use the "Redirect" directive to map your sub paths to actual paths
https://reacttraining.com/react-router/web/example/no-match
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
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 Commented May 11, 2018 at 0:12publicPath: '/'
specified in your webpack output. – Dennis Commented Aug 7, 2018 at 2:58