I have resolved this a few different ways in the past:
- Redirects using the IIS Rewrite Module, which is my preferred solution. It does require that the IIS Rewrite Module be installed in IIS, and it is a little more work than the Securities solution, but I prefer it because it changes the URL and caches the change as a permanent redirect, in case any online links point at the URL.
- Sitecore Securities, using the implementation described in @RichardSeal's solution. This is a great one because it requires no additional code and it is likely that your solution is already set up for it. This is definitely the simplest solution, but it does incur a high risk to your site's SEO.
- A
SiteResolvingItemNotFound
Processor, suggested by @HishaamNamooya in comments. Personally, I'm not a fan of this solution from a performance standpoint, but I have used it and it does work.
Pros and Cons: Using Sitecore Securities
This really is a great solution for a lot of reasons, but it is not without its drawbacks.
Pros
- Lowest effort and least complex
- Everything managed in Sitecore
- No deployment necessary (no AppPool recycle, no DLLs, no configs and no views)
Cons
- High risk to SEO: since you are permitting URLs with the Sitecore item path to resolve and access your site, you are opening your site up to possibly taking a pretty big SEO hit. As soon as one of the URLs with the item path is linked to from an external site or if you are using Google Analytics (GA) tracking code on your site then that URL will get registered with Google as a separate URL. Google will then infer that you have two URLs pointing to the same content (SEO hit) and it will also track each URL separately (SEO hit), so your SEO data will different for each URL.
Creating a SiteResolvingItemNotFound
Processor
The SiteResolvingItemNotFound
processor approach is a solid approach that gives you the most control over how you handle this issue, but it must be approached with care and with caution.
Pros
- Most control
- No risk to SEO
- Can resolve item in code to see what site it actually belongs to
- Can redirect to friendly URL of the item that was requested
- Reasonably low effort
Cons
- Performance: you will be running this on every request whether or not an item is resolved
- Requires config and DLLs changes, meaning a deployment
- Back-end code requires more effort to implement and deploy than a config change or Sitecore content update
Considerations when Implementing a SiteResolvingItemNotFound
Processor
If you do go this route, the first thing that you need to consider is the fact that performance is a major factor, since this is run on every request whether or not an item is resolved. As such, you need to make sure that you implement some form of caching so that your code is as performant as possible.
Second, if the item is not found on the site that is requested by the Sitecore item path, then you need to decide how you want to handle it. Do you redirect to a 404 page on the site requested by the hostname or by the Sitecore item path? In this case, it makes sense to do either, but IMHO the site requested via the hostname should take priority, since the item path really doesn't belong in the URL in the first place. The decision is yours, however.
Third, you want to capitalize on the benefits of going this route, meaning the fact that you have the greatest possible control and the ability to resolve your item in code to see what site it actually belongs to. Make sure that you do so, and redirect (permanently) to the friendly URL of the item on whatever site it actually does belong to.
Redirects using the IIS Rewrite Module
I'm not a fan of my sites' visitors using explicit Sitecore item paths in their URLs. If any do, I prefer to redirect the URL permanently to remove the explicit Sitecore item path from the URL so that it redirects now and in the future. I'm not alone in this either! Google, Bing and other search engines prefer that I do this too!
Remember that it is possible that those URLs come from external links to the site and that the URLs are valid, pointing to an item path like http://jack.com/sitecore/content/Jack/Home/Some/Page
. As such, rather than just redirecting URLs with explicit Sitecore item paths to a 404 not found page, a 401 unauthorized page or the homepage, I would rather redirect to the requested URL: http://jack.com/Some/Page
.
Pros
- Low effort
- Config based, rather than code-based
- No risk to SEO
- Very performant
- Can manage all sites with a single redirect (depending on your site configurations)
- Removes Sitecore item path from URL
- Redirects URLs permanently for SEO
- Prevents duplicate URLs pointing to the same content, which is better for SEO
Cons
- Have to install the IIS Rewrite Module for this to work (not usually a big deal, but if your client manages their server then this can mean extra communication overhead with IT and/or DevOps teams)
- Not managed inside of Sitecore (also not a big deal, IMHO)
- Config-based, so this does require a deployment and an AppPool recycle (manageable, but the Sitecore Securities solution wins from this perspective)
- If you already have IIS redirects on your site then you will want to avoid chaining redirects, since redirect chains are bad for SEO (resolved by the solution provided, below)
The last caveat is IMHO the only one that really matters. Note that if this is the only redirect on your site, then you really don't need to worry about redirect chaining. If you do have multiple redirects (which many sites do), however, then do not fret - read on for the solution to flatten your redirect chains.
SEO Matters
As previously stated, it's not just me who prefers that we drop the Sitecore item path from the URL: Search engines, like Google and Bing, prefer it too. Your site's SEO will take a hit if you have different external links pointing to the same page content. For example, if you allow both of the following to work, then your site's SEO will take a hit if there is at least one external link pointing to each:
http://jack.com/sitecore/content/Jack/Home
http://jack.com/
This is where using the IIS Rewrite module for this solution out-matches the Sitecore Securities solution.
Desired Behavior
Let's look at a few examples of the desired redirect behavior that our implementation should provide:
Example 1:
This example answers the question of who is more important: the site requested via the hostname or the site requested via the Sitecore item path? IMHO, since the Sitecore item path doesn't belong in the URL in the first place, the site requested via the hostname should take priority. As such, we have the following:
http://jack.com/sitecore/content/Jill/Home/Tom/Blog
=>
http://jack.com/Tom/Blog
If http://jack.com/Tom/Blog
doesn't exist then the visitor will be sent to a 404 not found page.
Note that if the behavior in this example is not what you are looking for then you can stop reading. You will either want to use Sitecore Securities or create a SiteResolvingItemNotFound
processor.
Example 2:
This example answers the question of what will happen if your Sitecore item path points to a page that actually is within the site you request. In my opinion, these URLs should work but be permanently redirected to the URL without the Sitecore item path:
http://jack.com/sitecore/content/Jack/Home/Some/Page/That/Exists
=>
http://jack.com/Some/Page/That/Exists
The permanent redirect will be cached by browsers and search engines, so users will no longer see /sitecore/content
in the path. This is better for SEO because now you enforce that you only have one link that points to that page's content, as far as search engines are concerned.
Using the IIS Rewrite module
The IIS Rewite module is easy to install, low effort to configure and works great for pattern-based redirects like this one. There are a few reasons why this is my preferred solution:
- It is a very low effort solution (though a little more work than just using securities)
- You can use a single rewrite rule to make the redirect work for all of your sites, provided that they all have a Sitecore path like
/sitecore/content/<site_root>/Home
. If any are different, however, you may need to add more rules or else you may be able to customize a single rule to work
- If
/sitecore/content
is in a URL pointing to the context site then this solution will redirect that URL permanently to remove the Sitecore path
The Rewrite Rule
Below is an example rewrite rule that should work. Note that I wasn't able to find a project that currently has these rules (probably just in my archives), but the below (yet untested) rule should do the trick:
<rewrite>
<rules>
<rule name="Redirect Explicit Sitecore Path">
<match url="^sitecore/content/([a-z0-9-_]+)/home(/(.*))?$" />
<action type="Redirect" url="{R:3}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
In the above, we specify that our rewrite rule should match
any incoming URLs that start with the path (excluding host, etc.) sitecore/content/<some_item>/home
, with the rest of the path (if not empty) starting with a /
after home
(not appending to home
). Any incoming URLs that match this pattern will Redirect
to the rest of the URL after home
(if none, this redirects to the homepage).
Avoiding Redirect Chaining
Note that this section is borderline off-topic, but it is important to know so that you can mitigate the redirect chaining issue with this solution.
If you have more than one redirect rule on your site and you didn't know that chaining redirects is not good for SEO then you should give this article a good read.
How to Flatten Redirect Chains
In Section 3 of the linked article, the author explains how you can get IIS to flatten redirect chains created by IIS Rewrite Rules:
By using a slight modification to your existing rules to make them rewrites that prepend an underscore (_
) prefix to your rewritten URLs, you can add a single redirect rule that looks for rewritten, "flattenable" URLs and redirect them a single time.
The author provides three different rules that can be used (together, if necessary) to actually perform the redirect. In the case of this solution, we only need to apply the third redirect rule, but if you are using canonical redirects for the scheme (http
vs https
) or inclusion/exclusion of www
then you may need the other two, as well:
<rule name="SEO - non-canonical redirect" stopProcessing="true">
<match url="^(_+)(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_METHOD}" pattern="GET" />
</conditions>
<action type="Redirect" url="{R:2}" />
</rule>
In this rule, we match any URLs that start with one or more underscores (as each rewrite rule will prepend an additional _
prefix to the URL), and then redirect to the part of the URL that comes after the underscores.
Bear in mind that the only caveat to this is that your site cannot have any URLs that start with an underscore, as those URLs will be redirected to remove the underscores. This usually isn't a big deal, as most sites will not have URLs starting with an underscore. If you do have such URLs, however, you can simply update your rewrite rules to change the prepending of the underscore to rewritten URLs to something else and update the redirect rule to match.
Flattening Redirect Chains for Your New Redirect
Applying this to our solution, you would end up with the following in your Rewrite Rules configuration:
<rewrite>
<rules>
<rule name="Rewrite Explicit Sitecore Path - Flattened Redirect" stopProcessing="false">
<match url="^sitecore/content/([a-z0-9-_]+)/home(/(.*))?$" />
<action type="Rewrite" url="_{R:3}" />
</rule>
<!-- other "flattenable" rewrite rules here ... -->
<rule name="Execute Flattened Redirect: Non-canoncical" stopProcessing="true">
<match url="^(_+)(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_METHOD}" pattern="GET" />
</conditions>
<action type="Redirect" url="{R:2}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
There are a few things to note here:
- The
<rule stopProcessing="false">
property on the rewrite rule tells IIS that it should continue to apply more rules before it actually executes the action. This way, we flatten all of the rewrites and then execute the redirect using their result, rather than actually performing a rewrite. Apply to each redirect to flatten
- The original redirect was changed to the rewrite rule via
<action type="Rewrite">
because IIS Rewrite redirect rules don't support the <rule stopProcessing="false">
option, so we have to flatten the rewrites first and then execute the single redirect. Apply to each redirect to flatten
- The
<action url="{R:3}>
property of the rewrite rule was changed to <action url="_{R:3}">
in order to prepend the underscore prefix to the rewritten URL. Apply to each redirect to flatten
- In the redirect rule, we use the
<match url="^(_+)(.*)">
property to match URLs with one or more prepended underscores, since each rewrite will add another underscore prefix to your URLs
- In the redirect rule, we use the
<action url="{R:2}">
in order to redirect to the second capture group in the match URL, which is the URL without the prepended underscores
By Example: Understanding How the IIS Rewrite Rules in this Solution Work
In the OP, a visitor navigates to this URL and sees the content from the Jill
site on the Jack
site: http://jack.com/sitecore/content/Jill/Home/Tom/Blog
.
Using the IIS Rewrite module and the rules that we defined above, the examples below illustrate the step-by-step processing of the incoming URL.
Example 1:
In this example, we step through the processing of the rules that we defined, above.
Visitor's Browser
=>
http://jack.com/sitecore/content/Jill/Home/Tom/Blog
=>
IIS executes Rewrite Processing
=>
http://jack.com/sitecore/content/Jill/Home/Tom/Blog
=>
Process Rule: "Rewrite Explicit Sitecore Path - Flattened Redirect"
Matched with the following capture groups:
{R:0} = http://jack.com/sitecore/content/Jill/Home/Tom/Blog
{R:1} = Jill
{R:2} = /Tom/Blog
{R:3} = Tom/Blog
Action changes URL to: _{R:3}:
_{R:3} = _Tom/Blog
type = "Rewrite"
stopProcessing is false, so process next rule with result of action
=>
Process Rule... (other "flattenable" rewrite rules here)
Note: Since we did not define any, I will skip this and
we will just assume that no other flattenable
rewrite rules matched
=>
Process Rule: "Execute Flattened Redirect: Non-canoncical"
Matched with the following capture groups:
{R:0} = _Tom/Blog
{R:1} = _
{R:2} = Tom/Blog
Conditions Passed (page request is a GET request)
Action changes URL to: {R:2}
{R:2} = Tom/Blog
type = "Redirect"
redirectType = "Permanent"
Rule is a redirect so stop processing and execute
=>
IIS Redirects URL:
Status Code: 301 (Permanent)
Location: http://jack.com/Tom/Blog
Note that the request doesn't even make it to Sitecore. This actually helps with performance too. IIS by default also sends the 301 status code for a permanent redirect, because the redirectType
is configured as "Permanent". This ensures that there is no cost to SEO from the redirect.
Example 2:
In this example, we will step through the same rules that we defined above, but we will pretend that there were three other rewrite rules (flattened redirect rules) that matched and were applied to modify the URL based on a query string (note: assume that we had appendQueryString="true"
on all of our actions):
Visitor's Browser
=>
http://jack.com/sitecore/content/Jill/Home/Tom/Blog?y=2017&p=3
=>
IIS executes Rewrite Processing
=>
http://jack.com/sitecore/content/Jill/Home/Tom/Blog?y=2017&p=3
=>
Process Rule: "Rewrite Explicit Sitecore Path - Flattened Redirect"
Matched with the following capture groups:
{R:0} = http://jack.com/sitecore/content/Jill/Home/Tom/Blog?y=2017&p=3
{R:1} = Jill
{R:2} = /Tom/Blog?y=2017&p=3
{R:3} = Tom/Blog?y=2017&p=3
Action changes URL to: _{R:3}
_{R:3} = _Tom/Blog?y=2017&p=3
type = "Rewrite"
stopProcessing is false, so process next rule with result of action
=>
Process Rule: "Imaginary Rule 1 - Append Year to URL"
Matched with the following capture groups:
{R:0} = _Tom/Blog?y=2017&p=3
{R:1} = _Tom/Blog
Conditions Passed with the following capture groups:
{C:1} = ?
{C:2} = 2017
{C:3} = p=3
Action changes URL to: _{R:1}/{C:2}{C:1}{C:3}
_{R:1}/{C:2}{C:1}{C:3} = __Tom/Blog/2017?p=3
type = "Rewrite"
stopProcessing is false, so process next rule with result of action
=>
Process Rule: "Imaginary Rule 2 - Append Page Number to URL"
Matched with the following capture groups:
{R:0} = __Tom/Blog/2017?p=3
{R:1} = __Tom/Blog/2017
Conditions Passed with the following capture groups:
{C:1} = ?
{C:2} = 3
{C:3} = <empty>
Action changes URL to: _{R:1}/{C:2}{C:1}{C:3}
_{R:1}/{C:2}{C:1}{C:3} = ___Tom/Blog/2017/3?
type = "Rewrite"
stopProcessing is false, so process next rule with result of action
=>
Process Rule: "Imaginary Rule 3 - Remove Empty Query Strings"
Matched with the following capture groups:
{R:0} = ___Tom/Blog/2017/3?
{R:1} = __Tom/Blog/2017/3
Action changes URL to: __{R:1}
_{R:1} = ____Tom/Blog/2017/3
type = "Rewrite"
stopProcessing is false, so process next rule with result of action
=>
Process Rule: "Execute Flattened Redirect: Non-canoncical"
Matched with the following capture groups:
{R:0} = ____Tom/Blog/2017/3
{R:1} = ____
{R:2} = Tom/Blog/2017/3
Conditions Passed (page request is a GET request)
Action changes URL to: {R:2}
{R:2} = Tom/Blog/2017/3
type = "Redirect"
redirectType = "Permanent"
Rule is a redirect so stop processing and execute
=>
IIS Redirects URL:
Status Code: 301 (Permanent)
Location: http://jack.com/Tom/Blog/2017/3
404
or remain on the homepage of jack?httpBeginRequest
, you will check if the current url obtained it valid and contain the itemshttpRequestBegin
processor to validate sites and 404 out-of-site items.