I try to setup nested routes for my react app like this
/
-> Home Page/about
-> About Page/protected
-> protected default page/protected/page1
-> protected page 1It works fine in codesandbox (https://codesandbox.io/s/react-router-nested-route-utqy7) React 16.8.1 React Router 4.3.1
But when I set the same thing up with webpack-dev-server (3.7.1), it can only reach /
and can't reach to the rest routes.
My file structure is like
├── package.json
├── src
│ ├── index.jsx
│ └── index.html
├── webpack
│ ├── paths.js
│ ├── webpack.common.js
│ └── webpack.dev.js
└── webpack.config.js
paths.js
const path = require('path');
module.exports = {
outputPath: path.resolve(__dirname, '../', 'build'),
entryPath: path.resolve(__dirname, '../', 'src/index.jsx'),
templatePath: path.resolve(__dirname, '../', 'src/index.html'),
};
webpack.common.js
const webpack = require('webpack');
const convert = require('koa-connect');
const history = require('connect-history-api-fallback');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const commonPaths = require('./paths');
module.exports = {
entry: commonPaths.entryPath,
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /(node_modules)/,
},
],
},
serve: {
add: app => {
app.use(convert(history()));
},
content: commonPaths.entryPath,
dev: {
publicPath: commonPaths.outputPath,
},
open: true,
},
resolve: {
modules: ['src', 'node_modules'],
extensions: ['*', '.js', '.jsx', '.css', '.scss'],
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({
template: commonPaths.templatePath,
}),
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'async',
}),
],
};
webpack.dev.js
const webpack = require('webpack');
const commonPaths = require('./paths');
module.exports = {
mode: 'development',
output: {
filename: '[name].js',
path: commonPaths.outputPath,
chunkFilename: '[name].js',
},
module: {
rules: [
{
test: /\.(css|scss)$/,
use: [
'style-loader',
{
loader: 'css-loader',
},
'sass-loader',
],
},
],
},
devServer: {
contentBase: commonPaths.outputPath,
compress: true,
hot: true,
},
plugins: [new webpack.HotModuleReplacementPlugin()],
};
webpack.config.js
const webpackMerge = require('webpack-merge');
const common = require('./webpack/webpack.common');
const devConfig = require(`./webpack/webpack.dev.js`);
module.exports = webpackMerge(common, devConfig);
index.jsx
import React from "react";
import { render } from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
const Homepage = () => (
<div>
<h1>Home Page</h1>
</div>
);
const AboutPage = () => (
<div>
<h1>About</h1>
</div>
);
const Protected = () => (
<div>
<h1>Protected default page</h1>
</div>
);
const ProtectedPage1 = () => (
<div>
<h1>ProtectedPage1</h1>
</div>
);
render(
<BrowserRouter>
<div>
<Route path="/" component={Homepage} exact />
<Route path="/about" component={AboutPage} />
<Route
path="/protected"
render={({ match: { url } }) => (
<div>
<Route path={`${url}/`} component={Protected} exact />
<Route path={`${url}/page1`} component={ProtectedPage1} />
</div>
)}
/>
</div>
</BrowserRouter>,
document.getElementById('app')
);
I think some paths are incorrect in my config, I just can't figure out where is wrong.
); export default App; If you open your React application and navigate between both paths, everything should be fine.
With React Router, you have two options for creating nested routes. The first is using the /* with nested <Routes> pattern and the second is using the <Outlet /> pattern.
The main difference between the two is the way they store the URL and communicate with your web server. A <BrowserRouter> uses regular URL paths. These are generally the best-looking URLs, but they require your server to be configured correctly.
To fix React Router v4 routes not working, we may need the exact prop to make React Router search for an exact path match. to add the exact prop so that going to / will render the Home component. To fix React Router v4 routes not working, we may need the exact prop to make React Router search for an exact path match.
In order to serve your React application on every path, you need to tell Webpack and its configuration that it should fallback for every path to your entry point: ... ... Now you should be able to navigate via the browser's URL bar to /about. I hope this helps anyone who stumbles across this issue.
To recap, nested routes allow you to, at the route level, have a parent component control the rendering of a child component. Twitter's /messages route is the perfect example of this. With React Router, you have two options for creating nested routes.
Namely, you need to be comfortable with two of React Router's most foundational components – Route and Routes. Let's start with Route. Put simply, Route allows you to map your app's location to different React components. For example, say we wanted to render a Dashboard component whenever a user navigated to the /dashboard path.
I finally figured out the reason that webpack-dev-server couldn't serve nested routes.
As a single page application, when you visit /somepath
of your react app, it actually fallback to the /
and pass the pathname to react router. React router will navigate you to /somepath
by the using browser's history API.
webpack-dev-server, for some unknown reason, doesn't enable this "fallback to history API" behaviour by default.
So, we need to add historyApiFallback: true,
to the devServer
of webpack config.
Now, all top level routes, like /somepath
should work, but for nested routes, like /somepath/morepath
, it's not enough.
With default webpack-dev-server setting, the compiled html template will point to the bundled js like <script type="text/javascript" src="main.js"></script>
. Pay attention to the src="main.js"
which assumes the main.js
is under the same path as the index.html
. The assumption is true for top level path /somepath
but for nested routes, /somepath/morepath
, this assumption will lead html file to access main.js
like /somepath/main.js
.
So, we end up with looking for a way to specify a certain place for html file when it's going to access the bundled js. And, it's the job of publicPath
. Adding publicPath: '/',
to the output block of webpack config. It will tell html to always access main.js
from /
folder and the compiled html will be <script type="text/javascript" src="/main.js"></script>
. That's exactly what we're looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With