8

I have 2 sites named Jack and Jill. Below is the site definition for each:

name="Jack" hostName="jack.com|www.jack.com" enableTracking="true" virtualFolder="/"
      physicalFolder="/" rootPath="/sitecore/content/Jack" startItem="/Home" database="web" domain="extranet"
      allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB"
      filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false"
      cacheRenderingParameters="true" renderingParametersCacheSize="10MB"

name="Jill" hostName="jill.com|www.jill.com" enableTracking="true" virtualFolder="/"
      physicalFolder="/" rootPath="/sitecore/content/Jill" startItem="/Home" database="web" domain="extranet"
      allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB"
      filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false"
      cacheRenderingParameters="true" renderingParametersCacheSize="10MB"

The problem I running into is we can access the blog page for Jill from Jacks url like this: http://jack.com/sitecore/content/Jill/Home/Tom/Blog. I have been running through this blog post by Alex, and it makes sense by making, in my example, Jack and Jill the start item. However, that would give me an error because Jack and Jill are just "Site" items with no presentation details.

http://sitecoreblog.alexshyba.com/have_sitecore_content_in_your_urls_time_to_fix_it/

I have also tried moving all to my rootPath and leaving the startItem with "/" and that does work, but it doesn't solve my problem as I can still access Jill's page from Jacks url's.

Has anyone encountered this peculiarity before or am I missing a .config or path setting somewhere?

6
  • Should it return an Error 404 or remain on the homepage of jack? Commented Mar 24, 2017 at 19:30
  • It should return a 404 page not found error, or error out somehow. Commented Mar 24, 2017 at 19:41
  • Maybe, by implementing a NotFoundProcessor in the httpBeginRequest, you will check if the current url obtained it valid and contain the items Commented Mar 24, 2017 at 20:15
  • 1
    We use a httpRequestBegin processor to validate sites and 404 out-of-site items.
    – Kam Figy
    Commented Mar 24, 2017 at 20:25
  • So I am correct in assuming there is not functionality OOTB for Sitecore to handle this type of situation, and I would have to put something custom together? Commented Mar 24, 2017 at 20:41

2 Answers 2

7

The simplest way would be with security. If you give each site a unique domain and then simply deny read access to the {sitedomain}/Anonymous user it would give you what you want.

So the site definitions would become:

name="Jack" hostName="jack.com|www.jack.com" enableTracking="true" virtualFolder="/"
  physicalFolder="/" rootPath="/sitecore/content/Jack" startItem="/Home" database="web" 
  domain="JACK" allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" 
  registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB"
  filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" 
  enableDebugger="true" disableClientData="false"
  cacheRenderingParameters="true" renderingParametersCacheSize="10MB"

name="Jill" hostName="jill.com|www.jill.com" enableTracking="true" virtualFolder="/"
  physicalFolder="/" rootPath="/sitecore/content/Jill" startItem="/Home" database="web" 
  domain="JILL" allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" 
  registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB"
  filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" 
  enableDebugger="true" disableClientData="false"
  cacheRenderingParameters="true" renderingParametersCacheSize="10MB"

Then for all the content under /sitecore/content/Jack deny read access for JILL/Anonymous - and the same for the content under /sitecore/content/Jill for the JACK/Anonymous user.

1
  • Great answer! +1 for simplicity and not requiring a deployment or an AppPool recycle Commented Mar 25, 2017 at 16:36
6

I have resolved this a few different ways in the past:

  1. 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.
  2. 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.
  3. 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    
1
  • Kick ass answer, 4yrs later. Excellent answer on rewrite rules
    – Rob Scott
    Commented Nov 29, 2021 at 23:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.