Search code examples
reactjstypescriptroutesreact-routertsx

React Router with deeper paths


I am trying to get an example with React Router running.

My example has the 3 following routes:

  1. (works in browser) http://0.0.0.0:8081/one
  2. (fails in browser) http://0.0.0.0:8081/one/two
  3. (fails in browser) http://0.0.0.0:8081/one/two/three

All routes work with Link, but only 1. works when typing the url in the browser. When typing i.e. the 2. route in the browser, the browser console responds the following error:

GET http://0.0.0.0:8081/one/app.js net::ERR_ABORTED 404 (Not Found)

Main App class:

import * as React from 'react';
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';
import { One } from './One';
import { Two } from './Two';
import { Three } from './Three';

export class App2 extends React.Component<{}, {}> {
    public render() {
        return <BrowserRouter>
            <ul>
                <li>
                    <Link to='/one'>One</Link>
                </li>
                <li>
                    <Link to='/one/two'>Two</Link>
                </li>
                <li>
                    <Link to='/one/two/three'>Three</Link>
                </li>
            </ul>
            <Switch>
                <Route path='/one/two/three' component={Three} />
                <Route path='/one/two' component={Two} />
                <Route path='/one' component={One} />
            </Switch>
        </BrowserRouter>;
    }
}

Class named One:

import * as React from 'react';

export class One extends React.Component<{}, {}> {
    public render() {
        return <div>One</div>;
    }
}

Class named Two:

import * as React from 'react';

export class Two extends React.Component<{}, {}> {
    public render() {
        return <div>Two</div>;
    }
}

Class named Three:

import * as React from 'react';

export class Three extends React.Component<{}, {}> {
    public render() {
        return <div>Three</div>;
    }
}

Command to run the app in development mode:

"scripts": {
    "develop": "webpack-dev-server --mode development --open --port 8081 --host 0.0.0.0 --config webpack.dev.config.js"
}

The Webpack configuration webpack.dev.config.js:

const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const htmlPlugin = new HtmlWebPackPlugin({
    template: "./src/index.html",
    filename: "./index.html"
});

module.exports = {
    entry: "./src/index.tsx",
    output: {
        filename: "app.js",
        path: path.resolve(__dirname, "dist")
    },

    // Enable sourcemaps for debuggin webpack's output.
    devtool: "source-map",

    resolve: {
        // Add '.ts', and '.tsx' as resolvable exteensions.
        extensions: [".ts", ".tsx", ".js", ".json"]
    },

    module: {
        rules: [
            // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
            { test: /\.tsx?$/, loader: "awesome-typescript-loader" },

            // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
            { enforce: "pre", test: /\.js$/, loader: "source-map-loader" },

            {
                test: /\.css$/,
                use: [
                    { loader: "style-loader" },
                    { loader: "typings-for-css-module-loader", options: { modules: true, namedExport: true, camelCase: true, localIdentName: "[name]_[local]_[hash:base64]" }}
                ]
            },

            {
                test: /\.scss$/,
                exclude: /\.global.scss$/,
                use: [
                    { loader: "style-loader" },
                    { loader: "typings-for-css-modules-loader", options: { modules: true, namedExport: true, camelCase: true, localIdentName: "[local]" }},
                    { loader: "postcss-loader", options: { plugins: function () { return [ require("autoprefixer") ]; }}},
                    { loader: "sass-loader" }
                ]
            },

            {
                test: /\.scss$/,
                include: /\.global.scss$/,
                use: [
                    { loader: "style-loader" },
                    { loader: "css-loader" },
                    { loader: "postcss-loader", options: { plugins: function () { return [ require("autoprefixer") ]; }}},
                    { loader: "sass-loader" }
                ]
            }
        ]
    },

    devServer: {
        historyApiFallback: true,
        disableHostCheck: true
    },

    plugins: [
        new CleanWebpackPlugin({
            cleanAfterEveryBuildPatterns: ['dist']
        }),
        htmlPlugin
    ]
};

I use the following versions:

  • Node v13.7.0
  • "@types/react": "16.9.11",
  • "@types/react-dom": "16.9.4",
  • "@types/react-router-dom": "5.1.2",
  • "react": "16.11.0",
  • "react-dom": "16.11.0",
  • "react-router-dom": "5.1.2",
  • "typescript": "3.7.4",
  • "webpack": "4.41.2",
  • "webpack-cli": "3.3.10",
  • "webpack-dev-server": "3.9.0"

I tried to follow the examples here.

Why does only the 1. route work? Why don't the other routes 2. and 3. work?

Edit 1:

Trying to use exact does not work either. The result is the same as the above mentioned:

import * as React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { One } from './One';
import { Two } from './Two';
import { Three } from './Three';

export class App2 extends React.Component<{}, {}> {
    public render() {
        return <BrowserRouter>
            <Switch>
                <Route exact path='/one' component={One} />
                <Route exact path='/one/two' component={Two} />
                <Route exact path='/one/two/three' component={Three} />
            </Switch>
        </BrowserRouter>;
    }
}

Edit 2:

Trying to change the order does not work either. The result is the same as the above mentioned:

import * as React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { One } from './One';
import { Two } from './Two';
import { Three } from './Three';

export class App2 extends React.Component<{}, {}> {
    public render() {
        return <BrowserRouter>
            <Switch>
                <Route path='/one/two/three' component={Three} />
                <Route path='/one/two' component={Two} />
                <Route path='/one' component={One} />
            </Switch>
        </BrowserRouter>;
    }
}

Solution

  • Simply: the most specific route goes first. Just reverse your order.

    As with most routers each one is checked in sequential order for a match.

    Edit 1: evidence https://reacttraining.com/react-router/web/guides/primary-components

    Edit 2: Your 404 error indicates to me that the issue is not the router but the server. Did you build the server or is webpack-dev-server a premade server for serving while you develop? I think you'll find that if you go to /one and click a to /one/two it will actually work.

    Edit 3: Your webpack Dev server config needs something. I don't have experience with this, but here's a doc webpack.js.org/configuration/output/#outputpublicpath that I think should help.

    As suggested in the comments: The final solution is adding publicPath: '/' to output in the Webpack config.