Gatsby: Dynamic redirect matching

Created on 12 Dec 2018  ยท  13Comments  ยท  Source: gatsbyjs/gatsby

Summary

I want to be able to match paths based on the presence of a unique identifier. Think URL params but a little more flexible, like with regex. The rest of the URL shouldn't matter.

Basic example

Let's say I'm generating user pages with slugs that include both the username and an auto-generated ID, e.g. /users/this-is-my-username-777 where 777 is the ID. If this user changes their username, a new slug will be created: /users/a-better-username-777. Note the ID is the same because it's the same user, and we really don't care about the rest of the URL.

Motivation

This is good for supporting dynamic slugs, which might be derived from user-generated content. As Gatsby v2 has better support for larger dynamic sites or "apps" I think this would be a useful addition. Ideally content should be easily found and when slugs break, user experience suffers.

Other Considerations

The only thing I'm not too sure about is if this would be feasible on the server, but at least could be handled client-side. Also curious to hear if anyone has implemented a similar effect without requiring changes to Gatsby core. Thanks!

question or discussion

All 13 comments

Client side only routes

Gatsby v2 uses Reach Router under the hood which AFAIK doesn't support regular expressions for path matching.

Based on your example above though, I think you can get away with a Route with path set to users/:userSlug and custom logic within the rendered component to check for parts of userSlug.

Server side pages

You could use the createPages API to create pages in your app.

createPage takes a path and you can pass any arbitrary slugs (derived from user generated content etc).

Closing this for now but please feel free to reopen if there is anything else ๐Ÿ™‚

The problem with client-side only routes is when a user tries accessing an old slug _directly_, they'll get a 404. Consider the following use case:

  1. I'm a user and I have my profile page at /users/tim-12345
  2. I change my username from tim to timmy, which means my page is now at /users/timmy-12345
  3. Like any good user, I have my page shared on social media using the old slug, so my content would appear unavailable to my audience.

Client-side redirects can salvage this scenario, but currently there's a noticeable delay between 404 -> actual page.

Also, I don't think the router itself needs to support this. I'm hoping to have a more robust matching layer via createRedirect, but maybe it's not possible without deeper integration.

@sidharthachatterjee I followed the feature request template, so I was hoping to have more of a discussion about this feature rather than immediate closure ๐Ÿ˜•

I'm unable to reopen the issue, so should I submit a new one?

@timglorioso I've reopened this to continue the discussion. My apologies for closing it prematurely.

Client-side redirects can salvage this scenario, but currently there's a noticeable delay between 404 -> actual page.

It's true that client side redirects will always render a noticeable delay between the 404 and the redirected page.

This can be salvaged by

  • maintaining a list of historical redirects somewhere
  • fetching them during build (via some API etc) and using createRedirect to create all of them

You'd still have to maintain the redirects server side (in the hosting provider of your choice) of course.

We have a Netlify plugin at the moment that creates the required _headers file automatically.

However, I don't think this approach would scale because all those redirects would be included in the bundle and over time that could cause the bundle to balloon up in size.

Well, considering how powerful Gatsby is I thought it might be able to help us avoid maintaining a list of redirects ๐Ÿ˜„

I'm thinking of something like this for the redirect API:

createRedirect({
  fromPath: /^\/users\/.*-12345/, // would match any username for the ID `12345`
  toPath: '/users/the-newest-username-12345',
  redirectInBrowser: true
})

That being said, I don't know how redirects are handled internally, so this might be impossible.

Gatsby is very powerful indeed ๐Ÿ˜„

While we can implement the above, server side redirects will still depend on the hosting solution and I don't think most hosting providers support regular expressions in path matching (Netlify does not)

And plugins that use this redirect metadata will end up breaking

I see, makes sense. Could you elaborate on this proposed solution? Is this only applicable for client-side only routes? It seems pages rendered at build-time are immune to all usage of the Router, but I need these pages to be rendered for SEO.

Based on your example above though, I think you can get away with a Route with path set to users/:userSlug and custom logic within the rendered component to check for parts of userSlug.

Yes, that would only work on the client side (Router is only relevant on the client side)

If you need the pages to exist server side (for SEO or anything else), I suppose the only solution is to use createPage to create all the user pages, createRedirect to add all the custom redirects and separately add the redirects to your hosting solution as well so that they get served correctly.

Could you elaborate on this proposed solution?

Sure ๐Ÿ™‚ I meant something like

componentDidMount() {
  const { userSlug }  = this.props
  const userSlugParts = userSlug.split('-')
  const userId = userSlugParts[userSlugParts.length - 1] // Get the last part which is the ID in your example url

  getUserById(userId)
    .then((user) => {
      // Set state and render user etc
    })
}

Would only work client side of course

Gotcha. It looks like my best bet for now is matching paths on the 404 page and doing a client-side redirect. Feels kinda gross not doing server-side redirects, but maintaining a slugs list sounds a tad more gross ๐Ÿ˜…. Just hoping googlebot doesn't get too upset with me.

One last question about this:

While we can implement the above, server side redirects will still depend on the hosting solution and I don't think most hosting providers support regular expressions in path matching (Netlify does not)

What about for a simple Apache or nginx server? Redirecting based on regex matching can happen there, so I think this still might be worthwhile to consider, even if for the next major ๐Ÿคž

What about for a simple Apache or nginx server? Redirecting based on regex matching can happen there, so I think this still might be worthwhile to consider, even if for the next major ๐Ÿคž

This would break existing plugins (like Netlify) which don't support regex path matching. Our redirect matching needs to be the least common denominator amongst the support out there.

We could consider adding support behind a flag or something, perhaps. Will leave this open for the rest of the team's comments.

Thanks, @timglorioso

As it turns out, I achieved a pretty good result by using nginx configured to redirect to basic URLs with only the ID, like /user/12345, based on a captured regex match. I could go a step further and add a script to help redirect to the latest slug, but this works for now.

Static file hosting was appealing, but ultimately wasn't flexible enough for my use case. Tunnel vision at its finest ๐Ÿคฆโ€โ™‚๏ธ Just a matter of picking the right tool for the job. Thanks so much for your help! Gatsby rules.

Glad I could help @timglorioso

Happy building ๐Ÿ™‚

Was this page helpful?
0 / 5 - 0 ratings