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.mattcutts.com/blog/seo-advice-url-canonicalization/
http://support.google.com/webmasters/bin/answer.py?hl=en&answer=139394
Easy solution to what can become a big problem.
I like it!
Great topic!
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.
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?
For EPiServer 6 R2 is already out there. And for Epi 7, it will be in the add-on store shortly.
Great! Looking forward to it.