If you're using a headless CMS for storing content, you also work with URL slugs, the last parts of any URL. The problem is, content editors are able to freely change the slugs which can cause 404 errors, lost page ranks, broken links, and in the end confused visitors on your site. In this talk, I will present a solution for keeping a history of URL slugs in the CMS and explain how to implement a proper redirect mechanism (using TypeScript!) for dynamically generated pages on a Next.js website.
Add to the talk notes: https://github.com/ondrabus/kontent-boilerplate-next-js-ts-congress-2022
How to properly handle URL slug changes in Next.js
Transcription
Hello everyone, I'm Andrey, developer evangelist at Content by Kentico, and today I want to tell you how to properly handle URL slack changes in next.js. Now first of all, why should we care about URL slugs? Every content item that is stored in the headless CMS and you are actually storing it or displaying it as a page in your site implementation contains a URL slug. Now the problem is when editor comes into that content item, changes the URL slug, and your site implementation just takes the change, rebuilds the page, and the old page, the old URL slug just ceases to exist. The problem is if that page used to have a good page rank on search engines or you use that page in your marketing campaigns, all those visitors that are using that URL will now get 404 errors. And these days you cannot really afford confused visitors, right? So what we need to do is first we need to know for every page, every content item, we need to know the URL slug history. You see here the example is that the current page has a URL slug, hello typescript Congress, but in the past it used to have a URL slug, typescript Congress, and hello conference. We need to know the history in order to be able to issue the redirects properly. Next thing is for the next.js, now we're going to be using next.js and content in this example, but if you're using a different framework, you can apply the same principles there. Here we're just talking about next.js. So we have two methods, we have getStaticPaths and getStaticProps. In getStaticPaths we need to provide all the URL slugs where there is a page for us to generate. In that case, we need to provide all the three URL slugs. So that's the history ones and the current ones as well. And the last step, the third step is to add a proper redirect in getStaticProps. There we need to find out based on the slug whether this is a current slug like the hello typescript Congress, which is the target page, or whether the slug that we're using is a historic one and we should redirect the visitor to a current page. In this case, that would be the typescript Congress, hello conferences, which will issue redirects. So how can we do this in next.js? Let's take a look. Let's start in content first. On the content side of things, I prepared one content type called conference. And when you look at the content item, that's already using that content type. You see in content, we have a special URL slug history custom element that you can use on your project. It's available open source, so we can just deploy it to Netlify or Vercel and use it just like I'm using it here. And it actually tracks all the changes you do to a URL slug. So you see here, now the URL slug is typescript Congress 2022. If I change it to typescript Congress, it automatically registers the change. And when I publish the page, it's going to store the change within the content item. So you see this is in the history. Now we can do this as many times as we want. We're just going to get the list of strings out of the api. But this is the first step. You need to know all the URL slugs, the historic ones and the current ones. Now let's take a look at the implementation. In next.js, I have a simple page slug TSX. Now this whole thing is based on a next.js content boilerplate, so there's not a ton of functionality. But here you see there are two methods, getStaticPaths, getStaticProps. And as I mentioned in the presentation at the start, you need to provide all the paths to getStaticPaths or as a result to next.js. And in getStaticProps, you need to issue the redirect if it's a page that should be redirected. So, here we just need to do one simple change and add to elements parameter, add the history as well. So before we were only using the URL slug, but now we want to use the URL slug history as well to get really all the paths. And now we need to get all the paths in a simple list. Now the thing is, if we only get the paths in getStaticProps, we would have to do one additional api check with the content api to see if the slug is a historic one or a current one. So what I'm going to do, I'm going to create the list of all the slugs and all the current slugs that they should redirect to. Now I'm going to do the implementation here, I'm going to fast forward, and then I'm going to explain the code in a second. Right, so what I implemented here is we're getting all paths from the content CMS, and we're actually providing all those paths back as an IPagePath structure, which you can see contains just two properties, path and redirectsTo. Path is the URL slug, the current one, and redirectsTo is in case we're working with the historic path, here we're going to hold the current path of any page. So you see that for the actual item coming from content, we're providing a path that is the current path of the item, the one of the published version of the page, and then we're creating a structure of the historic paths, historic slugs, and they redirect all to the current page path. Now the problem is, we cannot really take the whole structure and provide it to NextJS so that we get it in getStaticProps. The reason is, NextJS will only allow us to transfer the slug because it's in the file name, it's the variable on the way to the page. So we're not going to be able to transfer this to getStaticProps and avoid the performance problems. So what we're going to do is we're going to do a workaround suggested by Versal, where we're just going to take all the paths, we're going to serialize them into a physical file, and then we're going to take that file back in getStaticProps. So I'm just going to again do the implementation and fast forward. Now you see here that I'm storing all the paths in the cache. The last thing I need to do here is to provide all the slugs, right, this is the second point from the presentation. We need to get all the paths, historic ones and current ones, and return it back to NextJS here, because we're just telling NextJS on this path, there is not a 404, there is a page here. And now we need to do the third step, and that is issue a proper redirect if we find out we should. So in this case, we're just taking the path from cache. And if there is a page that should be redirected, it will have the redirects to filled, because these are all the historic URL slugs, and we need to redirect them to this path. So we can do a simple condition here, if path redirects to exists. In that case, we want to do the redirect, so we're going to return a redirect object, and the redirect object only needs to contain a destination and permanent. So permanent, if you want to do 307 or 308 redirect, based on that set the permanent field, I want permanent to be true. And in the destination, just remember if you're in the subfolder or if you're in the root, this is going to be destination from the root of your site. So in my case, it's just a slash and path it redirects to. So let's save this. There is still a problem here, under get static paths. Right. I forgot. Yes, there is path dot path, because here we're getting just the object, so we want the slug in there. Now this is fine, so let's try to run the site. And let's go back to the browser and refresh this page. Now the current URL slug is typescript Congress. So on that URL slug, we should have the typescript Congress page. So you see this is working. And the previous slug was typescript Congress 2022. So if we append here 2022, we should get redirected to typescript Congress. So it works as expected. Now the implementation isn't ideal, to be honest, but it works this way and lets us put everything in order and does not, that's the most important thing, it does not require editors to do any extra steps. It just works. And that's all for me. If you want to get more information about this solution or you want to share your experience handling URL slugs, then definitely reach out to me. You can find me on Twitter. And I hope you enjoyed the rest of the conference.