Instead of 404 or the canonical header tag, why not just permanent redirect the pages to your preferred url. Then you are definitely getting your link juice transfered to the permanently redirected destination. The canonical header tag is more of a piece of advice for the search engines to consider. With a 301 redirect, you can't possibly choice the unprefered url.
Using nopCommerce 1.90 I have made the following modifications to accomplish this.
File: NopCommerceStore\Controls\BaseNopFrontendPage.cs Added string declaration in #region Fields
protected string strRedirectURL;
OnPreRender sub replaced with:
protected override void OnPreRender(EventArgs e)
{
//************************************************MOD
if (strRedirectURL != null && strRedirectURL != string.Empty)
Response.RedirectPermanent(strRedirectURL);
//************************************************
//java-script
string publicJS = CommonHelper.GetStoreLocation() + "Scripts/public.js";
Page.ClientScript.RegisterClientScriptInclude(publicJS, publicJS);
base.OnPreRender(e);
}
OnPreInit sub replaced with:
protected override void OnPreInit(EventArgs e)
{
//store is closed
if (this.SettingManager.GetSettingValueBoolean("Common.StoreClosed"))
{
if (NopContext.Current.User != null &&
NopContext.Current.User.IsAdmin &&
this.SettingManager.GetSettingValueBoolean("Common.StoreClosed.AllowAdminAccess"))
{
//do nothing - allow admin access
}
else
{
Response.Redirect("~/StoreClosed.htm");
}
}
//SSL
switch (this.SslProtected)
{
case PageSslProtectionEnum.Yes:
{
CommonHelper.EnsureSsl();
}
break;
case PageSslProtectionEnum.No:
{
CommonHelper.EnsureNonSsl();
//***************************************************MOD
string strURL = Request.Url.AbsoluteUri;
string strDomain = IoC.Resolve<NopSolutions.NopCommerce.BusinessLogic.Configuration.Settings.ISettingManager>().StoreUrl;
if (strURL.IndexOf("http://localhost") == -1 && strURL.IndexOf(strDomain) == -1)
{
string strRedirectPath = Request.RawUrl.Substring(1);
strRedirectURL = strDomain + strRedirectPath;
}
//*****************************************************
}
break;
case PageSslProtectionEnum.DoesntMatter:
{
//do nothing in this case
}
break;
}
//allow navigation only for registered customers
if (this.CustomerService.AllowNavigationOnlyRegisteredCustomers)
{
if (NopContext.Current.User == null || NopContext.Current.User.IsGuest)
{
if (!this.AllowGuestNavigation)
{
//it's not login/logout/passwordrecovery/captchaimage/register/accountactivation page (be default)
string loginURL = SEOHelper.GetLoginPageUrl(false);
Response.Redirect(loginURL);
}
}
}
//theme
if (!String.IsNullOrEmpty(NopContext.Current.WorkingTheme))
{
this.Theme = NopContext.Current.WorkingTheme;
}
base.OnPreInit(e);
}
File: Libraries\Nop.BusinessLogic\SEO\SEOHelper.cs GetProductUrl(Product product) replaced with:
public static string GetProductUrl(Product product)
{
if (product == null)
throw new ArgumentNullException("product");
string seName = GetProductSEName(product);
string url2 = SEOHelper.EnableUrlRewriting ? IoC.Resolve<ISettingManager>().GetSettingValue("SEO.Product.UrlRewriteFormat") : "{0}Product.aspx?ProductID={1}";
string url = string.Format(url2, IoC.Resolve<Configuration.Settings.ISettingManager>().StoreUrl, product.ProductId, seName);
return url.ToLowerInvariant();
}
GetCategoryUrl(Category category) replaced with:
public static string GetCategoryUrl(Category category)
{
if (category == null)
throw new ArgumentNullException("category");
string seName = GetCategorySEName(category);
string url2 = SEOHelper.EnableUrlRewriting ? IoC.Resolve<ISettingManager>().GetSettingValue("SEO.Category.UrlRewriteFormat") : "{0}Category.aspx?CategoryID={1}";
string url = string.Format(url2, IoC.Resolve<Configuration.Settings.ISettingManager>().StoreUrl, category.CategoryId, seName);
return url.ToLowerInvariant();
}
File: NopCommerceStore\Category.aspx.cs PageLoad replaced with:
protected void Page_Load(object sender, EventArgs e)
{
if (category == null || category.Deleted || !category.Published)
Response.Redirect(CommonHelper.GetStoreLocation());
//title, meta
string title = string.Empty;
if (!string.IsNullOrEmpty(category.LocalizedMetaTitle))
title = category.LocalizedMetaTitle;
else
title = category.LocalizedName;
SEOHelper.RenderTitle(this, title, true);
SEOHelper.RenderMetaTag(this, "description", category.LocalizedMetaDescription, true);
SEOHelper.RenderMetaTag(this, "keywords", category.LocalizedMetaKeywords, true);
//canonical URL
if (SEOHelper.EnableUrlRewriting &&
this.SettingManager.GetSettingValueBoolean("SEO.CanonicalURLs.Category.Enabled"))
{
if (!this.SEName.Equals(SEOHelper.GetCategorySEName(category)) || Request.RawUrl.ToLower().IndexOf("/category.aspx") != -1)
{
string canonicalUrl = SEOHelper.GetCategoryUrl(category);
if (this.Request.QueryString != null)
{
for (int i = 0; i < this.Request.QueryString.Count; i++)
{
string key = Request.QueryString.GetKey(i);
if (!String.IsNullOrEmpty(key) &&
(key.ToLowerInvariant() != "categoryid") &&
(key.ToLowerInvariant() != "sename"))
{
canonicalUrl = CommonHelper.ModifyQueryString(canonicalUrl, key + "=" + Request.QueryString[i], null);
}
}
}
strRedirectURL = canonicalUrl;
//SEOHelper.RenderCanonicalTag(Page, canonicalUrl);
}
}
if (!Page.IsPostBack)
{
NopContext.Current.LastContinueShoppingPage = CommonHelper.GetThisPageUrl(true);
}
}
File: NopCommerceStore\Product.aspx.cs PageLoad replaced with:
protected void Page_Load(object sender, EventArgs e)
{
if (product == null || product.Deleted)
Response.Redirect(CommonHelper.GetStoreLocation());
//title, meta
string title = string.Empty;
if (!string.IsNullOrEmpty(product.LocalizedMetaTitle))
title = product.LocalizedMetaTitle;
else
title = product.LocalizedName;
SEOHelper.RenderTitle(this, title, true);
SEOHelper.RenderMetaTag(this, "description", product.LocalizedMetaDescription, true);
SEOHelper.RenderMetaTag(this, "keywords", product.LocalizedMetaKeywords, true);
//canonical URL
if (SEOHelper.EnableUrlRewriting &&
this.SettingManager.GetSettingValueBoolean("SEO.CanonicalURLs.Products.Enabled"))
{
if (!this.SEName.Equals(SEOHelper.GetProductSEName(product)) || Request.RawUrl.ToLower().IndexOf("/product.aspx") != -1)
{
string canonicalUrl = SEOHelper.GetProductUrl(product);
strRedirectURL = canonicalUrl;
//SEOHelper.RenderCanonicalTag(Page, canonicalUrl);
}
}
if (!Page.IsPostBack)
{
this.ProductService.AddProductToRecentlyViewedList(product.ProductId);
}
}
This now also handles when product or category pages are requested via actual path and query (eg. /product.aspx?productid=2292&sename=example-product-name&)
This will also ensure that your domain is always the same as the value supplied in Store Url of administration/globalsettings.aspx (eg. with or without www.). For all front end pages that ensure non-ssl.
I have done this in a way that ensures there won't be chained redirects between the preferred domain and preferred rewritten url.
Let me know if anyone has any thoughts or concerns.