Gatsby: Issue with matchPath sorting for nested paths

Created on 10 Sep 2019  路  7Comments  路  Source: gatsbyjs/gatsby

Description

With the updated matchPath sorting from https://github.com/gatsbyjs/gatsby/pull/17411, nested client only routes are not correctly displayed.

I believe it is also related to this issue: https://github.com/gatsbyjs/gatsby/issues/9705

Steps to reproduce

Code Sandbox

  1. Go to /admin
  2. Click "User 123"

Expected result

You are navigated to /admin/user/123 and AdminUser component is displayed

match-paths.json should look like this

    {
        "path": "/admin/",
        "matchPath": "/admin/*"
    },
    {
        "path": "/admin/users/users/",
        "matchPath": "/admin/*"
    },
    {
        "path": "/admin/users/user/",
        "matchPath": "/admin/*"
    }

Actual result

Url changes to /admin/user/123, but the AdminUsers component is displayed

match-paths.json looks like this:

    {
        "path": "/admin/users/users/",
        "matchPath": "/admin/*"
    },
    {
        "path": "/admin/users/user/",
        "matchPath": "/admin/*"
    },
    {
        "path": "/admin/",
        "matchPath": "/admin/*"
    },

Environment

System:
OS: macOS 10.14.6
CPU: (4) x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.8.0 - /usr/local/bin/node
Yarn: 1.17.3 - /usr/local/bin/yarn
npm: 6.9.0 - /usr/local/bin/npm
Browsers:
Chrome: 76.0.3809.132
Firefox: 68.0.2
Safari: 12.1.2
npmPackages:
gatsby: ^2.15.14 => 2.15.14
gatsby-image: ^2.2.18 => 2.2.18
gatsby-plugin-manifest: ^2.2.16 => 2.2.16
gatsby-plugin-offline: ^2.2.10 => 2.2.10
gatsby-plugin-react-helmet: ^3.1.7 => 3.1.7
gatsby-plugin-sharp: ^2.2.21 => 2.2.21
gatsby-source-filesystem: ^2.1.22 => 2.1.22
gatsby-transformer-sharp: ^2.2.14 => 2.2.14

awaiting author response bug

Most helpful comment

@dopeters let me start by apologize, in my comment it was written in a haste as i was really tired and basically i probably mislead you as you don't actually need the plugin.

With that out of the way i took a new look with "fresh eyes" (pardon the bad pun) to the example you supplied and it seems that you want to create client-side routing, probably something in the nature of this and this.

Based on that and your code, it seems that probably the problem itself resides in the implementation of the client side routing. Once again based on your code, you want to funnel the client side routes to /admin and all subsequent routes go from there.

You can achieve this by simplifying the way pages are handled in the onCreatePage api call in gatsby-node.js and some work done to the code.

Going to enumerate the steps i took to achieve this.

  • Modified gatsby-node.js from:
exports.onCreatePage = ({ page, actions }) => {
  const { createPage } = actions

  if (page.path.match(/^\/admin/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }
  if (page.path.match(/^\/admin\/user/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }
  if (page.path.match(/^\/admin\/users/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }

  if (page.path === "/") {
    createPage({
      ...page,
      matchPath: "/*",
    })
  }
}

to


exports.onCreatePage = async ({ page, actions }) => {
  const { createPage } = actions
  if (page.path.match(/^\/admin\/admin/)) {
    page.matchPath = `/admin/*`
    createPage(page)
  }

}

With that all of the traffic incoming to /admin/ will be funneled directly into the router and let it do his job and show the what needs to be shown.

  • Moved the router component admin.js inside /src/pages/ inside /src/pages/admin.
  • Updated the code in admin.js to reflect the changes and now looks like:
import React from "react"
import { Router } from "@reach/router"
import AdminPage from "../../components/admin/admin"
import AdminUser from "../../components/admin/user"
import AdminUsers from "../../components/admin/users"

const AdminIndex = () => {
  return (
    <Router basepath="/">
      <AdminPage path="admin" />
      <AdminUser path="admin/user/:userId" />
      <AdminUsers path="admin/users" />
    </Router>
  )
}

export default AdminIndex

As you can see by the code snippet the pages that the router will handle were moved. They are now inside /src/components/admin/*. Did this, because despite being pages, they are now treated as components and i wanted to keep it simple and clean, only leaving the router there.

All of the code of the pages is exactly the same.

  • Issued gatsby build && gatsby serve to get a production build and emulate a production server. Opened up http://localhost:9000/admin and i'm presented with:

do_peters_rev_1

  • Clicking the link there, yelds the following:

do_peters_rev_2

  • Going directly to http://localhost:9000/admin/users yelds the following:

do_peters_rev_3

  • Created the repo, expanded the code you supplied with one extra example.

  • Pushed the code to a netlify site and with that i was getting some errors with the redirection. I don't know what will be your case, but take a look at the netlify.toml file for the redirects to prevent a 404 being shown and then flashes to the actual page and content.

Feel free to provide feedback so that we can close this issue or continue to work on it untill we find a suitable solution.

Once again sorry for the misleading information.

All 7 comments

@dopeters I've created new gatsby site based on the hello world starter and grabbed the code you posted. And i can confirm this. In the meantime depending on your use case and timeframe i would go with gatsby-plugin-create-client-paths, if you want i can update the code based on the plugin and hoist it to a repo and you can take a look at it at your own pace. Also i'm going to ping @wardpeet to see if he can chime in and possibly help you out better.

I'm not quite sure if this is bug. There are 3 routes with /admin/* matchPath so any of those are equally suitable to render /admin/user/123 route (there is nothing we can use to decide whether to use one or the other).

Maybe we could display warning if same matchPath is used for multiple routes (?)

@pieh it seems that what's intended is to funnel all the requests to the router inside /admin/ and let it deal with it. Probably this is more a edge case and a possible solution for this case is actually use the plugin.

@jonniebigodes The same thing happens in the actual app I found this issue in, which does use that plugin. If you are able to make an example with that plugin that works, I'd be very interested in seeing that.

Appreciate your help!

@dopeters let me start by apologize, in my comment it was written in a haste as i was really tired and basically i probably mislead you as you don't actually need the plugin.

With that out of the way i took a new look with "fresh eyes" (pardon the bad pun) to the example you supplied and it seems that you want to create client-side routing, probably something in the nature of this and this.

Based on that and your code, it seems that probably the problem itself resides in the implementation of the client side routing. Once again based on your code, you want to funnel the client side routes to /admin and all subsequent routes go from there.

You can achieve this by simplifying the way pages are handled in the onCreatePage api call in gatsby-node.js and some work done to the code.

Going to enumerate the steps i took to achieve this.

  • Modified gatsby-node.js from:
exports.onCreatePage = ({ page, actions }) => {
  const { createPage } = actions

  if (page.path.match(/^\/admin/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }
  if (page.path.match(/^\/admin\/user/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }
  if (page.path.match(/^\/admin\/users/)) {
    createPage({
      ...page,
      matchPath: "/admin/*",
    })
  }

  if (page.path === "/") {
    createPage({
      ...page,
      matchPath: "/*",
    })
  }
}

to


exports.onCreatePage = async ({ page, actions }) => {
  const { createPage } = actions
  if (page.path.match(/^\/admin\/admin/)) {
    page.matchPath = `/admin/*`
    createPage(page)
  }

}

With that all of the traffic incoming to /admin/ will be funneled directly into the router and let it do his job and show the what needs to be shown.

  • Moved the router component admin.js inside /src/pages/ inside /src/pages/admin.
  • Updated the code in admin.js to reflect the changes and now looks like:
import React from "react"
import { Router } from "@reach/router"
import AdminPage from "../../components/admin/admin"
import AdminUser from "../../components/admin/user"
import AdminUsers from "../../components/admin/users"

const AdminIndex = () => {
  return (
    <Router basepath="/">
      <AdminPage path="admin" />
      <AdminUser path="admin/user/:userId" />
      <AdminUsers path="admin/users" />
    </Router>
  )
}

export default AdminIndex

As you can see by the code snippet the pages that the router will handle were moved. They are now inside /src/components/admin/*. Did this, because despite being pages, they are now treated as components and i wanted to keep it simple and clean, only leaving the router there.

All of the code of the pages is exactly the same.

  • Issued gatsby build && gatsby serve to get a production build and emulate a production server. Opened up http://localhost:9000/admin and i'm presented with:

do_peters_rev_1

  • Clicking the link there, yelds the following:

do_peters_rev_2

  • Going directly to http://localhost:9000/admin/users yelds the following:

do_peters_rev_3

  • Created the repo, expanded the code you supplied with one extra example.

  • Pushed the code to a netlify site and with that i was getting some errors with the redirection. I don't know what will be your case, but take a look at the netlify.toml file for the redirects to prevent a 404 being shown and then flashes to the actual page and content.

Feel free to provide feedback so that we can close this issue or continue to work on it untill we find a suitable solution.

Once again sorry for the misleading information.

@jonniebigodes Thank you this was extremely helpful. Looks like changing the directory of those pages does solve the issue we're seeing. Gonna go ahead and close this out. Appreciate all the help!

@dopeters no need to thank, just glad i was able to help and you were able to solve the issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andykais picture andykais  路  3Comments

signalwerk picture signalwerk  路  3Comments

theduke picture theduke  路  3Comments

3CordGuy picture 3CordGuy  路  3Comments

KyleAMathews picture KyleAMathews  路  3Comments