BlogSEO redirects in .NET + Optimizely

SEO redirects in .NET + Optimizely

SEO.NETOptimizely
8 May 2024

It's way more complicated to be unique nowadays. Having a website is one of the main ways to represent the company and basically, a must-have. But as important as to HAVE the website, it is also crucial to MAKE it easily searchable, especially in an environment of strong competition.

Photo by Merakist on Unsplash

Apart from that, the website must also be highly positioned in the search results and to make it happen, it must meet many of the SEO conditions.

It usually ends with making the website follow one common pattern in the URLs, such as allowing only lowercase characters, ending (or not) with trailing slashes, or forcing the user to use www at the beginning of the URI. Thankfully, .NET comes with easy-to-use middleware to add rewrite rules, helping meet the SEO requirements.

All that needs to be done is to add URL Rewrite middleware in the Configure method of the Startup file.

       app.UseRewriter(new RewriteOptions()
            .SkipStaticFiles()
            .SkipCmsUrls()
            .AddRedirectToTrailingSlashesPermanent()
            .AddRedirectToLowercaseUrlPermanent()
            .SkipRemainingRulesIfDxp()
            .AddRedirectToWwwPermanent());

Worth mentioning is that the order matters and the first one executed will be the top one, so it might be good to put the most frequently used rule there.

The rules in the example above use extension methods so the Startup file is clean and easy to read. The actual logic can be seen in the implementation of the methods. The first rule skips the static files, so the middleware doesn't apply the next rules when it discovers any file extension.

public static RewriteOptions SkipStaticFiles(this RewriteOptions options)
    {
        options.Add(context =>
        {
            bool hasFileExtension = Regex.IsMatch(context.HttpContext.Request.Path.Value, @".*/[^.]*\.[\d\w]+$");

            if (hasFileExtension)
            {
                context.Result = RuleResult.SkipRemainingRules;
            }
        });

        return options;
    }

The next one is related to the CMS environment where we don't want to force any redirects which probably will result in breaking the platform. It adds URLs patterns that should not be redirected by the next rules. We can see URLs like util or episerver known from Optimizely CMS. It is also important to add URLs of any of the external tools installed in the CMS, like Localization DB provider or GETA Sitemaps. They can malfunction without those rules.

    public static RewriteOptions SkipCmsUrls(this RewriteOptions options)
    {
        options.Add(context =>
        {
            if (context.HttpContext.Request.Path.StartsWithSegments("/util") ||
                context.HttpContext.Request.Path.StartsWithSegments("/episerver") ||
                context.HttpContext.Request.Path.StartsWithSegments("/modules") ||
                context.HttpContext.Request.Path.StartsWithSegments("/localization-admin") ||
                context.HttpContext.Request.Path.StartsWithSegments("/getaoptimizelysitemaps"))
            {
                context.Result = RuleResult.SkipRemainingRules;
            }
        });

        return options;
    }

Next, we have the trailing slashes rule which is just a simple regex redirection

    public static RewriteOptions AddRedirectToTrailingSlashesPermanent(this RewriteOptions options)
    {
        options.AddRedirect("(.*[^/])$", "$1/", (int)HttpStatusCode.MovedPermanently);
        return options;
    }

The AddRedirectToLowercaseUrlPermanent is a bit more complicated as it requires the creation of a custom implementation of the Microsoft.AspNetCore.Rewrite.IRule interface, which exposes a method called ApplyRule

public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var path = context.HttpContext.Request.Path;
        var host = context.HttpContext.Request.Host;

        if (path.HasValue && path.Value.Any(char.IsUpper) || host.HasValue && host.Value.Any(char.IsUpper))
        {
            var response = context.HttpContext.Response;
            response.StatusCode = StatusCode;
            response.Headers[HeaderNames.Location] =
                (request.Scheme + "://" + host.Value + request.PathBase.Value + request.Path.Value).ToLower() +
                request.QueryString;
            context.Result = RuleResult.EndResponse;
        }
        else
        {
            context.Result = RuleResult.ContinueRules;
        }
    }

The last rule is built in .NET and adds a permanent redirect to www. When doing this in the Optimizely environment called DXP it is important to remember that this redirection can break access by using the default DXP URLs. This is the main reason why before the www redirection rule there was a rule checking if there is a URL other than the DXP one. The rule is another implementation of the IRule which looks like this:

public void ApplyRule(RewriteContext context)
    {
        var host = context.HttpContext.Request.Host;

        if (host.HasValue && host.Value.EndsWith(".dxcloud.episerver.net"))
        {
            context.Result = RuleResult.SkipRemainingRules;
        }
        else
        {
            context.Result = RuleResult.ContinueRules;
        }
    }

That would be all. Now, our website can get a few more points in the global SEO race.

More articles

Real life color picker
OptimizelyContent GraphHeadless

Exposing Color Picker to Content Graph

A guide on how to consume custom CMS property backing types in a headless architecture, using a color picker as an example.

Rubik's Cube as metaphor for hard problems to solve.
OptimizelyHeadlessError

Getting 404 when expecting 401

A short story about the mysterious behavior of an API in headless architecture.

Bunch of decimal numbers
OptimizelyGraphCommerce.NET

Decimal numbers in Optimizely Graph

Storing prices as decimal numbers on a commerce website and planning to expose them through Optimizely Graph? It might not be as straightforward as it seems.

Have a question?

Don't hesitate, and send me an email

smutek.damian95@gmail.com