Janaka Fernando
Mar 13, 2013
  13352
(2 votes)

Adding Canonical support to EPiServer sites

Avoiding Duplicate Content

I think everyone loves the Simple Address feature in EPiServer CMS to easily set up friendly URLs.  If you haven't come across it, Simple Address lets you set a simple URL that will come after the site domain.  This is useful if you have a long URL to the page and want to print or send a short URL  - for example a landing page that you want to direct traffic to.

So for the example here I would have two URLs

http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/

simply becomes

http://janaka.uk.episerver.com/Forward/

Now while this is great, it produces two unique URLs with identical content... a big no-no with Google page ranking. To avoid getting penalized for this, you need to set the canonical link to let the search engine know which URL is the primary source of this content to index.

Setting a canonical URL is pretty simple, its a single <link> tag that needs to go into your <head> tag.  So for the above example we'd simply need either of the following:

Absolute <link rel="canonical" href="http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/" />
or
Relative <link rel="canonical" href="/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/" />

 

Adding Canonicalization Support to EPiServer

So we need to add a canonical link whenever a page has a Simple Address.  I'm using the new Alloy sample code base for my examples here.  I want to modify the <head> section so looking at the existing code this is best achieved through the master page base class - SiteMasterPage

Below is an excerpt from the SiteMasterPage.cs file with my modifications

 

public abstract class SiteMasterPage : System.Web.UI.MasterPage
    {
        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);
 
            if (Master != null)
            {
                return;
            }
 
            SetupMetaTags();
            SetupCanonicalURL();
        }
 
        private void SetupCanonicalURL()
        {
            // Add canonical url meta tag for simple address
            if (!string.IsNullOrEmpty(CurrentPage.ExternalURL) &&
                string.Compare(CurrentPage.ExternalURL, 
                 Request.Url.Segments.Last().Replace("/", ""), true) == 0)
            {
                var urlBuilder = new UrlBuilder(CurrentPage.LinkURL);
 
                if (EPiServer.Global.UrlRewriteProvider.ConvertToExternal(urlBuilder,                                                             
                                    CurrentPage.ContentLink,
                                    System.Text.Encoding.Default))
                {
                    string sourceUrl = urlBuilder.Uri.ToString();
                    var canonicalLink = new HtmlLink();
                    canonicalLink.Attributes.Add("rel", "canonical");
                    canonicalLink.Href = sourceUrl;
 
                    Page.Header.Controls.Add(canonicalLink);
                }
            }
     }
}

Some points to note here.  The API provides a property CurrentPage.ExternalURL which will have the simple address.  If this is present I then check if this is the current URL ( as opposed to the long name URL ).  Finally the rest if fairly straightforward, using the UrlBuilder to construct the external link to the page, then writing this to the Link.  Note that in this example I am using relative links, because CurrentPage.LinkURL is relative, but you could build up the full path and use absolute links using Uri.AbsoluteUri.

Language Fallback & Replacement

In addition to the Simple Address, another place to provide automatic support for canonical URLs is when using language replacement or fallback.  In this case the URL will be different depending on the language, but the site will contain duplicated content. 

I've refactored the previous code sample and added a second condition to the SiteMasterPage SetupCanonicalLink method, checking if the current page's language is different from what is expected.  If it's different I load the source content and update the LinkURL to indicate the source language. 

 

private void SetupCanonicalURL()
        {
            // Add canonical url meta tag for simple address
            if (!string.IsNullOrEmpty(CurrentPage.ExternalURL) &&

string.Compare(CurrentPage.ExternalURL,

Request.Url.Segments.Last().Replace("/", ""), true) == 0)

            {
                var urlBuilder = new UrlBuilder(CurrentPage.LinkURL);
                WriteCanonicalLink(urlBuilder);
            }    
 
 
            // Add canonical url meta tag for language fallback/replacement 
            if (CurrentPage.Language != ContentLanguage.PreferredCulture)
            {
                var sourcePage = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<IContentRepository>()
                                                                                 .Get<PageData>(CurrentPage.ContentLink, 
                                         new LanguageSelector(CurrentPage.Language.Name));
                var sourceUrl = UriSupport.AddLanguageSelection(sourcePage.LinkURL, CurrentPage.Language.Name);
                var sourceUrlBuilder = new UrlBuilder(sourceUrl);
 
                WriteCanonicalLink(sourceUrlBuilder);
            }
        }
 
        private void WriteCanonicalLink(UrlBuilder url)
        {
            
            if (EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, CurrentPage.ContentLink, System.Text.Encoding.Default))
            {
                string sourceUrl = url.Uri.ToString();
                var canonicalLink = new HtmlLink();
                canonicalLink.Attributes.Add("rel", "canonical");
                canonicalLink.Href = sourceUrl;
 
                Page.Header.Controls.Add(canonicalLink);
            }
        }

 

Further reading:

http://www.seomoz.org/blog/canonical-url-tag-the-most-important-advancement-in-seo-practices-since-sitemaps

http://www.mattcutts.com/blog/seo-advice-url-canonicalization/

http://support.google.com/webmasters/bin/answer.py?hl=en&answer=139394

Mar 13, 2013

Comments

Petter Klang
Petter Klang Mar 13, 2013 01:41 PM

Easy solution to what can become a big problem.
I like it!

Mar 13, 2013 02:26 PM

Great topic!

Mar 14, 2013 10:42 AM

Yes that is correct Daniel, Mogul SEO Manager does have this canonical function out of the box (http://www.youtube.com/watch?v=zp-cNCgVnN0). It also have alternate language reference, Sitemap.xml functionality, historical URL redirection etc. All this is solved with one add-on.

Mar 14, 2013 12:25 PM

Hi Daniel and Ove, great to hear more about your SEO Manager product. EPiServer are positioning Mogul as a commercial add-on for the solutions you mentioned ( http://www.episerver.com/AddOns/Mogul-SEO-Manager/ )

Any plans on making this available to trial through the add-on store?

Mar 14, 2013 12:56 PM

For EPiServer 6 R2 is already out there. And for Epi 7, it will be in the add-on store shortly.

Mar 14, 2013 03:25 PM

Great! Looking forward to it.

Please login to comment.
Latest blogs
Optimizely community meetup - Sept 29 (virtual + Melbourne)

Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP's see the...

Ynze | Sep 27, 2022 | Syndicated blog

Preview multiple Visitor Groups directly while browsing your Optimizely site

Visitor groups are great - it's an easy way to add personalization towards market segments to your site. But it does come with it's own set of...

Allan Thraen | Sep 26, 2022 | Syndicated blog

The Report Center is finally back in Optimizely CMS 12

With Episerver.CMS.UI 12.12.0 the Report Center is finally re-introduced in the core product.

Tomas Hensrud Gulla | Sep 26, 2022 | Syndicated blog

Dynamic Route in ASP.NET Core When MapDynamicControllerRoute Does Not Work

Background Creating one of the add-on for Optimizely I had to deal with challenge to register dynamically route for the API controller. Dynamic rou...

valdis | Sep 25, 2022 | Syndicated blog