Tag: Redirect
Creating Multiple Dynamic Routes with NextJs

Creating Multiple Dynamic Routes with NextJs

If you’ve used NextJs then you will probably be familiar with how it handles routing using the file system.

Files named page.tsx are organized into folders and the name of the folder becomes the name of the route. E.g. If the folder were named about-us then the route on the site will be /about-us. To create a page under another page, simply create a folder within a folder. E.g. company/about-us/page.tsx becomes /company/about-us.

NextJs Routing Structure

Page names can also be dynamic, to do this you use a catch-all route which is defined with square brackets in the folder name. e.g. [slug]/page.tsx.

NextJs Routing Structure with dynamic route

Now all requests to the route of the site which don’t match a fixed path will be picked up by this page and will include a parameter matching the name within the square backets (in this example “slug”), which you can then use to render the page dynamically.

1export default function Page({ params }: { params: { slug: string } }) {
2 return <div>My Page Name: {params.slug}</div>
3}

Dynamic pages can also be nested. E.g. [category]/[product]/page.tsx. In this example a request to /electronics/50-inch-tv will route to the page including the parameters category=electronics, and product=50-inch-tv.

By using triple dots in-front of the parameter name NextJs will catch all requests to subpages and provide the hierarchy as an array to the parameter. E.g. […slug]/page.tsx will capture requests to /company/about-us and have a parameter named slug with the values [‘company’, ‘about-us’].

This is all explained on the NextJs site in detail https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#catch-all-segments

However, the one thing you can’t do is have two or more dynamic paths at the same level. E.g. Creating…

/[category]/page.tsx

/[slug]/page.tsx

Will lead to an error saying "Error: You cannot use different slug names for the same dynamic path".

The official way to have more than one dynamic path is to separate them by placing one into a subfolder, so in my scenario I could have…

/products/[category]/page.tsx

/[slug]/page.tsx

Now any routes starting /products/ will be routed to the category page rather than the slug page. However this typically doesn’t match the requirement and hasn’t been needed for the majority of sites being built with a CMS.

Creating multiple dynamic paths in NextJs using one catch all page

The first option for enabling multiple dynamic paths at the same level is to have one catch-all routes and within that file figure out what the page should be. E.g. /[…slug]/page.tsx

Now all requests to the site will be captured by this route and within the page you can write custom logic to determine if the first value is a category or generic page.

To keep code clean the actual rendering of the different page types can be split into separate components with page.tsx handling the logic for which to include.

This setup works well, however it has a tradeoff that a lot of functionality provided by NextJs through it’s folder structure is lost. E.g. NextJs support layout files at different levels of the file system but here you would be restricted to just the one.

Creating multiple dynamic paths in NextJs using rewrites

An alternative method is to structure the dynamic pages into separate subfolders as per the official way and then hide this using Rewrites.

What is a Rewrite?

A rewrite is like a redirect, but one that happens server side and is therefore invisible to the user. When a request goes into the server it is first checked against the rewrite rules and if it matches one, the request is rewritten to that URL rather than being passed as it to the application.

Anyone old enough to remember programming in Classic ASP may remember this being one of the methods first used to implement friendly URLs rather than having query strings for everything dynamic.

How to setup a rewrite in NextJs

Rewrites are configured in the next.config.mjs file.

1module.exports = {
2 async rewrites() {
3 return {
4 beforeFiles: [
5 {
6 source: '/electronics/:slug*',
7 destination: '/products/electronics/:slug*',
8 },
9 ]
10 },
11 },
12}

In this example I am creating a re-write for anything going to a path starting /electronics/ to route the request to the path /products/electronics/

Hardcoding the list of categories as re-writes isn’t much better than creating a lot of hard coded sub-folders, but this is easily fixed by dynamically writing the list which will happen when the site is built.

Creating a redirect in NextJs

The solution now works, however any requests to the /products/ url will also resolve as duplicate pages. To overcomes this we can create a standard redirect to put people back on our preferred path.

1module.exports = {
2 async redirects() {
3 return [
4 {
5 source: '/products/electronics/:slug*',
6 destination: '/electronics/:slug*',
7 permanent: true,
8 },
9 ]
10 },
11}

One thing to be aware of

While this is a solution that will work, one thing to note is that there may be a limit to how far it will scale. If you hit the point of creating over 1000 redirects or rewrites the NextJs build script will start warning of potential performance hits from there being to many redirects.

This isn’t something I’ve experienced, but each rewrite is effectively another check that needs to be performed against each request.

Sitecore Alias as Redirect

Sitecore Alias as Redirect

One feature of Sitecore that I have always disliked is Alias's. On each page of a site, content editors have the ability to click an alias button on the presentation tab and add alternative urls for the page.

Once added these will appear in the Aliases folder under system.

However all this accomplishes is multiple URLs existing for one page which is a big SEO no no.

Content editors like to do this in order to create simple URLs for things like landing pages. e.g. himynameistim.com/Sitecore but search engines hate it as they see multiple pages with the exact same content. As a result the value of each page gets lowered and appears lower in search engine results. What Content editors really want is to set up a 301 redirect so that they can have the simple URL but redirect users to the actual page on the site.

Aliases as Redirects

One solution is to updated the aliases functionality to cause a redirect to it's linked item rather than resolve the page.

To do this we need to create a pipeline processor that inherits from AliasResolver.

1using Sitecore; using Sitecore.Configuration; using Sitecore.Diagnostics; using Sitecore.Pipelines.HttpRequest; using System.Net; using System.Web; using AliasResolver = Sitecore.Pipelines.HttpRequest.AliasResolver; namespace HiMyNameIsTim.Pipelines { public class AliasAsRedirectResolver : AliasResolver { public override void Process(HttpRequestArgs args) { if (!Settings.AliasesActive) { return; // if aliases aren't active, we really shouldn't confuse whoever turned them off } var database = Context.Database; if (database == null) { return; // similarly, if we don't have a database, we probably shouldn't try to do anything } if (!Context.Database.Aliases.Exists(args.LocalPath)) { return; // alias doesn't exist } var targetID = Context.Database.Aliases.GetTargetID(args.LocalPath); // sanity checks for the item if (targetID.IsNull) { Tracer.Error("An alias for \"" + args.LocalPath + "\" exists, but points to a non-existing item."); return; } var item = args.GetItem(targetID); if (database.Aliases.Exists(args.LocalPath) &amp;&amp; item != null) { if (Context.Item == null) { Context.Item = item; Tracer.Info(string.Concat("Using alias for \"", args.LocalPath, "\" which points to \"", item.ID, "\"")); } HttpContext.Current.Response.RedirectLocation = item.Paths.FullPath.ToLower() .Replace(Context.Site.StartPath.ToLower(), string.Empty); HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.MovedPermanently; HttpContext.Current.Response.StatusDescription = "301 Moved Permanently"; HttpContext.Current.Response.End(); } } } }

And patch in in place of the regular Alias Resolver.

1<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
2<sitecore> <pipelines>
3<httpRequestBegin> <processor type="HiMyNameIsTim.Core.Pipelines.AliasAsRedirectResolver, LabSitecore.Core" patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel']"/> </httpRequestBegin>
4</pipelines> </sitecore>
5</configuration>

The above code is adapted from a solution given by Jordan Robinson but with a bug fixed to stop every valid URL without an alias writing an error to the log file.

Redirect to https using URL Rewrite

Redirect to https using URL Rewrite

There's always been reasons for pages to be served using https rather than http, such as login pages, payment screens etc. Now more than ever it's become advisable to have entire sites running in https. Server speeds have increased to a level where the extra processing involved in encrypting page content is less of a concern, and Google now also gives a boost to a pages page ranking in Google (not necessarily significant, but every little helps).

If all your pages work in https and http you'll also need to make sure one does a redirect to the other, otherwise rather than getting the tiny page rank boost from Google, you'll be suffering from having duplicate pages on your site.

Redirecting to https with URL Rewrite

To set up a rule to redirect all pages from is relatively simple, just add the following to your IIS URL Rewrite rules.

1<rule name="Redirect to HTTPS" stopProcessing="true">
2 <conditions>
3 <add input="{HTTPS}" pattern="^OFF$" />
4 </conditions>
5 <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" />
6</rule>

The conditions will ensure any page not on https will be caught and the redirect will do a 301 to the same page but on https.

301 Moved Permanently or 303 See Other

I've seen some posts/examples and discussions surrounding if the redirect type should be a 301 or a 303 when you redirect to https.

Personally I would choose 301 Moved Permanently as you want search engines etc to all update and point to the new url. You've decided that your url from now on should be https, it's not a temporary redirection and you want any link ranking to be transfered to the new url.

Excluding some URL's

There's every chance you don't actually want every url to redirect to https. You may have a specific folder that can be accessed on either for compatibility with some other "thing". This can be accomplished by adding a match rule that is negated. e.g.

1<rule name="Redirect to HTTPS" stopProcessing="true">
2 <match url="images" negate="true" />
3 <conditions>
4 <add input="{HTTPS}" pattern="^OFF$" />
5 </conditions>
6 <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" />
7</rule>

In this example any url with the word images in would be excluded from the rewrite rule.

Creating 301 redirects in web.config

For various reasons at times you may need to create a 301 redirect to another URL. This could be as a result of a page moving or you just need to create some friendly URLS.

As a developer you may be tempted to do something like this in code...

1private void Page_Load(object sender, System.EventArgs e)
2{
3 Response.Status = "301 Moved Permanently";
4 Response.AddHeader("Location","http://www.new-url.com");
5}

But do you really want your project cluttered up with files who's only purpose is to redirect to another page!

You may also be tempted to try doing something with .NET's RouteCollection. This would certainly solve an issue on creating a redirect for anything without a file extension, but there is a better way.

In your web.config file under the configuration node create something like this

1<location path="twitter">
2 <system.webServer>
3 <httpRedirect enabled="true" destination="http://twitter.com/TwitterName" httpResponseStatus="Permanent" />
4 </system.webServer>
5</location>

The location path specifies that path on your site that this redirect will apply to. The destination value in the httpRedirect is where the redirect will go to. As well as setting Permanent for the httpResponseStatus you can also specify Found or Temporary depending on your needs.