Dynamic Client Side Routing in Gatsby [solved]

Hi all,

I have a seemingly simple question but it’s proving tricky. In Gatsby I want to set up dynamic client-side routing like so:

/services/:id

So the context is, I have a /services page with a list of services. Then the user would click a list item to go to that service.

  • service123
  • service234

For example, these page would be clickable and lead to the respective /services/service123 page.

I know there is a client-side plugin for this, I’ve tried it , but the problem is that it won’t allow me to have the root /services page and link dynamically off that.

I know this is a simple task, but I’m wondering why there isn’t any clear docs or demo’s on this seemingly simple process.

I’m looking at the reach router docs and can see this example, but I can’t see it being used anywhere in Gatsby? Is it OK to do it this way?

import { Match } from "@reach/router"

const App = () => (
  <Match path="/hot/:item">
    {props =>
      props.match ? (
        <div>Hot {props.match.item}</div>
      ) : (
        <div>Uncool</div>
      )
    }
  </Match>
)

Any help would be really appreciated,

Thank you

SOLUTION

Ok, I solved this in two ways:

  • The server-side way (preferred)

This involved a lot more groundwork than the client-side method, but it is far superior. Basically the idea is to create a JSON file and reference it via GraphQL, generate a unique slug and loop over each JSON item slug to create a new page.

Just follow this tutorial link exactly: https://www.gatsbyjs.org/tutorial/part-seven/. By doing it this way you get all the benefits of Server Side Rendering (SSR), as well as querying with GraphQL, which make it a really performant and powerful approach.

  • The client-side way

This was my initial way of doing things. Looking back now, it seems “hacky”. But of course there might be use cases where it makes sense. If you have a list of data on a page, you can render a <Link /> component and point it to a wildcard route (I used the id for each mapped element). You can also pass some state through as well.

For example:

<Link to={`/user/${user.id}`} state={{user}}>
   {user.first_name}
</Link>

In the gatsby-node config I added this: code:

// Option One: The pages data comes from the `user-list` component.
// A Link component is passed a user id in the url (ie) `/users/${user.id}`.
// That's how the ID is rendered on the page. Additionally, state can be passed
// in with the Link component too. However, if the user refreshes the page, that data is lost.
exports.onCreatePage = async ({page, actions}) => {
  const {createPage} = actions

  if (page.path.match(/^\/user/)) {
    await createPage({
      path: '/user',
      matchPath: '/user/:id',
      component: path.resolve(`src/components/graphql/user.tsx`),
    })
  }
}

The major downside to this is that you don’t get all the benefits of SSR with Gatsby. I actually had to add this to my code so it would build for production:

const isSSR = typeof window === 'undefined'
  if (isSSR) {
    return <div>Loading ...</div>
  }

So, I would strongly recommend Option One. I hope that helps :slight_smile:

Would any of these solutions with Reach Router work? I will give it a try
https://reach.tech/router/example/nested-routes

https://reach.tech/router/api/Match

Did you try structuring your pages like this? Assuming your services are static, and not fetched from the database:

  • src/pages/services/index.js - // will match '/services'
  • src/pages/services/developlement.js - // will match '/services/developlement'
  • src/pages/services/design.js - // will match '/services/design'
  • src/pages/services/consulting.js - // will match '/services/consulting'

And there is no need to use router here.

But if your pages are dynamic, i.e you’re fetching these services from a database during build time, then you’d need to use the createPage function in gatsby-node.js. Just like you’d create the blogs in gatsby.

And to navigate to this, you’d use gatsby link.

import { Link } from 'gatsby'
...
<Link to="/services/design">Design</Link>

Clicking the above link will lead to the design service page.

Yeah pages are dynamic, and I’m just trying to do a test where I map over some dummy data and go to each new page. Even when using gatsby-node, following this example:

exports.onCreatePage = async ({ page, actions }) => {
  const { createPage } = actions
  // page.matchPath is a special key that's used for matching pages
  // only on the client.
  if (page.path.match(/^\/services/)) {
    page.matchPath = "/services/*"
    // Update the page.
    createPage(page)
  }
}

It allows me to have a /services page but not a /services and a /services/123 page … Not sure though

I think I’m just trying to go against the grain with Gatsby. If I have data that I am mapping over, then I probably should try and make it queryable or at a minimum, accessible to the gatsby-node.js file so I can generate those pages with their subsequent component and pass in the props/id to that component… Not sure?

That’s what I was suggesting. If you have the ‘data’ to create the pages dynamically, then just create them in gatsby-node with createPage api.
You don’t necessarily have to make you data queryable, you can just fetch it (or import it if you have a json file, for example) and feed it to createPage.

This might help: https://www.gatsbyjs.org/docs/using-gatsby-without-graphql/

And if you have some static data, you can just structure it as such in your pages directory.

I’ve not seen the manual routing in gatsby, especially at the whole page level, it works out of the box and there is no need to matching the routes. That’s why there are no examples of such thing in the docs.

Cool, it’s starting to make a bit more sense now. Thanks!
Solution added to the question above :slight_smile: