Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Router v4 nested routes not work with webpack-dev-server

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 1

It 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.

like image 908
Bing Lu Avatar asked Jun 13 '19 04:06

Bing Lu


People also ask

Does React Router work with Webpack?

); export default App; If you open your React application and navigate between both paths, everything should be fine.

Does React Router allows for nested routes?

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.

What is the difference between BrowserRouter and Router in React?

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.

How to fix react router V4 routes not working?

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.

How to serve react application on every path in Webpack?

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.

What are nested routes in react router?

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.

What do I need to know before using React router?

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.


Video Answer


1 Answers

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.

like image 161
Bing Lu Avatar answered Sep 27 '22 22:09

Bing Lu