skip to Main Content

In the documentation for react-router v6 (https://reactrouter.com/en/main/route/route) it says

Omitting the path makes this route a "layout route". It participates in UI nesting, but it does not add any segments to the URL

So why doesn’t this work?

const App = () =>
  <Routes>
    <Route path='/abc' element={<p>abc</p>} />
    <Route element={<MyComponent />}></Route>
  </Routes>
    
const MyComponent = () =>
  <Routes>
    <Route path='/def' element={<p>def</p>} />
  </Routes>

In this example, navigating to "/def" gives the error:

No routes matched location "/def"

If I add path='*' to the route referencing MyComponent then it works OK, but it seems the documentation is telling me I don’t need to do this (and in fact the docs don’t mention path='*' as far as I can see).

3

Answers


  1. In your example, you have defined MyComponent as a layout route by omitting the path prop, but you still need to specify a route for it to handle nested routes. If you want MyComponent to handle the nested route /def, you need to specify that route within MyComponent. Here’s how you can modify your code to make it work:

    import { Routes, Route } from 'react-router-dom';
    
    const App = () => (
      <Routes>
        <Route path='/abc' element={<p>abc</p>} />
        <Route element={<MyComponent />} />
      </Routes>
    );
    
    const MyComponent = () => (
      <Routes>
        <Route path='/def' element={<p>def</p>} />
      </Routes>
    );
    
    export default App;
    

    In this modified code, MyComponent is still defined as a layout route without a path, but within MyComponent, there’s a nested Route component that handles the /def route. Now navigating to /def should display the content defined in the <p>def</p> element.

    Login or Signup to reply.
  2. When you omit the path attribute for a <Route> in React Router v6, you’re indeed turning it into a "layout route". This means the route will act as a container for nested routes, participating in the UI hierarchy without contributing any path segment to the URL. However, for a nested route to be accessible, its parent route (the layout route, in this case) must be matched first. Your configuration misses this because you’ve placed <MyComponent /> as a direct child of <Routes> without a path, meaning it’s not directly accessible via the URL structure.

    Here’s why navigating to /def doesn’t work as expected in your setup:

    1. React Router tries to match the URL /def against your route configuration.
    2. It finds no route for /def at the top level (<Route path='/abc' element={<p>abc</p>} /> is the only route with a path, and it doesn’t match).
    3. Since your <Route element={<MyComponent />}></Route> does not specify a path, it’s considered a layout route but has no opportunity to match because it’s not nested within another route—it’s at the top level.

    The solution involves structuring your routes to use nesting properly. If you want MyComponent to serve as a layout for nested routes, you must ensure it’s accessible through some path. Adding path='*' makes MyComponent match any path not matched by other routes, effectively serving as a fallback route. However, this might not be what you want if MyComponent is supposed to be a specific part of your application rather than a catch-all.

    A better approach could be to explicitly define a path for MyComponent as a layout route or to nest your routes in a way that aligns with your application’s structure. For example:

    const App = () => (
        <Routes>
            <Route path='/abc' element={<p>abc</p>} />
            <Route path="/*" element={<MyComponent />}>
                <Route path='def' element={<p>def</p>} />
                {/* You can add more nested routes here */}
            </Route>
        </Routes>
    );
    
    const MyComponent = () => (
        <Routes>
            <Route index element={<p>MyComponent Index Page</p>} />
            <Route path='def' element={<p>def</p>} />
            {/* More nested routes can be added here if needed */}
        </Routes>
    );
    

    This configuration makes MyComponent accessible through any path (due to path="/*"), with def being a nested route within it. Note that using path="/*" at the top level makes MyComponent act as a catch-all container, which might not be ideal depending on your app’s structure. It’s usually better to define more specific paths for your layout routes.

    Login or Signup to reply.
  3. Issue

    So why doesn’t this work?

    const App = () =>
      <Routes>
        <Route path='/abc' element={<p>abc</p>} />
        <Route element={<MyComponent />}></Route>
      </Routes>
        
    const MyComponent = () =>
      <Routes>
        <Route path='/def' element={<p>def</p>} />
      </Routes>
    

    I think you are conflating what layout routes do with nested routes with descendent routes that any routed component can render further down the ReactTree. A pathless layout route like this doesn’t participate in route matching, so it’s expecting nested routes to be declared so that they can participate.

    const App = () =>
      <Routes>
        <Route path='/abc' element={<p>abc</p>} />
        <Route element={<MyComponent />}>
          {/* no nested routes to match 🤷🏻‍♂️ */}
        </Route>
      </Routes>
    

    Explanation

    Layout Routes

    When using layout routes you directly wrap and render nested routes. Layout routes serve the purpose of generally providing some "common UI" and an Outlet for the nested routes to render their content into.

    Example using layout routes.

    const App = () => (
      <Routes>
        <Route path='/abc' element={<p>abc</p>} />
        <Route element={<MyComponent />}> // <-- parent route
          <Route                          // <-- nested route
            path='def'                    // <-- "/def"
            element={<p>def</p>}          // <-- rendered into Outlet
          />
        </Route>
      </Routes>
    );
    
    const MyComponent = () => (
      <div>
        {/* Common UI header, etc */}
        <Outlet />
        {/* Common UI footer, etc */}
      </div>
    );
    

    Descendent Routes

    Layout routes play a non-existent role when rendering descendent routes.

    One very important distinction though is that in order for descendent routes to be reachable and matchable the parent route must append the wildcard matcher, or splat, to its path.

    Descendent routes build their paths relative to their parent route. This is why your code worked when you added path="*" to the parent route. This will match anything by "/abc" which is matched by the first route.

    Example using descendent routes.

    const App = () => (
      <Routes>
        <Route path='/abc' element={<p>abc</p>} />
        <Route     // <-- parent route
          path="*" // <-- match anything but "/abc"
          element={<MyComponent />}
        />
      </Routes>
    );
    
    const MyComponent = () => (
      <div>
        {/* Common UI header, etc */}
        <Routes>
          <Route       // <-- descendent route
            path='def' // <-- "/" + "def" -> "/def"
            element={<p>def</p>}
          />
        </Routes>
        {/* Common UI footer, etc */}
      </div>
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search