I have website I want to create SEO tags for in Umbraco. I was wondering how it is done? are there any best practice documents or advice?
I am not a SEO expert, but hopefully the code snippets below could get you started
On the pages I have added some properties. If you do it by the document type, by inheriting or by compositions you could choose. I have the following properties defined.
Page title, which I aim to have a bit different than the page name. Not sure if it makes any difference - however I hope it will make some more of my focus words for the article either appear in the page title, or the page name. The page name i place in the <head>
part, and the page title is used as a part of the article/main content.
Page snippet, which I aim to have as short as possible, and mostly less than 160 characters. The page snippet is used in the article, as well as a summary for metadata.
Page tags, used for metadata keywords as well as for horizontal content navigation on the site.
Featured image, although not strictly a part of SEO, it is important as a part of the strategy to make the content friendly for social media.
Author, might be of importance for SEO, and I have a property for the main author, and as a member property I register facebook profile page.
I have started writing a razor macro, however it needs some more work, however works good for me. I place it as an macro run in the <head>
part.
@inherits Umbraco.Web.Macros.PartialViewMacroPage
@{
string domain = "https://" + HttpContext.Current.Request.Url.Host;
string site_name = "sitename";
string og_title = CurrentPage.Name;
string og_image = "";
string og_description = "Description here";
string facebookPageAuthor = "https://www.facebook.com/xx";
string facebookPageSite = "https://www.facebook.com/xx";
string authorName = "asdf";
int authorId = 1099;
string url = domain + CurrentPage.Url;
string facebookAppId = "12341234";
string twitterUserAuthor = "@asdf";
string twitterUserSite = "@qwer";
string logoUrl = domain + "/media/1006/logo.png";
DateTime createDate = CurrentPage.CreateDate;
DateTime updateDate = CurrentPage.UpdateDate;
if (CurrentPage.pageTitle != null && !(CurrentPage.pageTitle is Umbraco.Core.Dynamics.DynamicNull))
{ og_title = CurrentPage.pageTitle;}
@* Check if this page has snippet, and use it exists *@
if (CurrentPage.pageSnippet != null && !(CurrentPage.pageSnippet is Umbraco.Core.Dynamics.DynamicNull))
{ og_description = CurrentPage.pageSnippet; }
@* Check if this page has featured image, and crop to facebook preferred crop size (1200x630px). Use parent page default image it exists *@
if (CurrentPage.featuredImage != null && !(CurrentPage.featuredImage is Umbraco.Core.Dynamics.DynamicNull))
{
var featImage = Umbraco.TypedMedia((int)CurrentPage.featuredImage);
og_image= featImage.GetCropUrl("1200x630"); }
else
{
og_image = Umbraco.Media(CurrentPage.AncestorsOrSelf(1).First().featuredImage).GetCropUrl("1200x630");
}
@* Check if author has facebook page *@
if ((int)CurrentPage.author >0)
{
authorId = (int)CurrentPage.author;
}
var authorModel = Members.GetById(authorId);
authorName = (string)authorModel.Name;
facebookPageAuthor = (string)authorModel.GetProperty("facebookProfilePage").Value;
}
<meta property="og:title" content="@og_title" />
<meta property="og:site_name" content="@site_name" />
<meta property="og:url" content="@url" />
<meta property="og:description" content="@og_description" />
<meta property="og:image" content="@domain@og_image" />
<meta property="fb:app_id" content="@facebookAppId" />
<meta property="og:type" content="article" />
<meta property="og:locale" content="en_US" />
<meta property="article:author" content="@facebookPageAuthor" />
<meta property="article:publisher" content="@facebookPageSite" />
<meta name="twitter:title" content="@og_title" />
<meta name="twitter:description" content="@og_description" />
<meta name="twitter:image:src" content="@domain@og_image" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@twitterUserSite" />
<meta name="twitter:creator" content="@twitterUserAuthor" />
<script type="application/ld+json">
{
"@@context": "http://schema.org",
"@@type": "NewsArticle",
"mainEntityOfPage":{
"@@type":"WebPage",
"@@id":"@url"
},
"headline": "@og_title",
"image": {
"@@type": "ImageObject",
"url": "@domain@og_image",
"height": 630,
"width": 1200
},
"datePublished": "@createDate.ToString("o")",
"dateModified": "@updateDate.ToString("o")",
"author": {
"@@type": "Person",
"name": "@authorName"
},
"publisher": {
"@@type": "Organization",
"name": "domain.com",
"logo": {
"@@type": "ImageObject",
"url": "@logoUrl",
"width": "660",
"height": "675"
}
},
"description": "@og_description"
}
</script>
A macro for making a breadcrumb with Microdata is useful for SEO.
@using Umbraco.Web
@using Umbraco.Web.Mvc
@using Umbraco.Core
@using System.Web
@inherits Umbraco.Web.Macros.PartialViewMacroPage
@*
This snippet makes a breadcrumb of parents using an html ordered list.
It makes metadata available for search engines in the Microdata format.
The CSS is customised for Bootstrap 4
*@
@if (Model.Content.Ancestors().Any())
{
var pageAncestors = Model.Content.Ancestors().OrderBy("Level");
<div>
<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">
@foreach (var page in pageAncestors)
{
<li class="breadcrumb-item" itemprop="itemListElement" itemscope
itemtype="http://schema.org/ListItem">
<a itemscope itemtype="http://schema.org/Thing"
itemprop="item" href="@page.Url">
<span itemprop="name">@page.Name</span>
</a>
<meta itemprop="position" content="@page.Level" />
</li>
}
<!-- And add the current page -->
<li class="breadcrumb-item active" itemprop="itemListElement" itemscope
itemtype="http://schema.org/ListItem">
<span itemprop="name">@Model.Content.Name</span>
</li>
</ol>
</div>
}
I sitemap should be submitted to the search engines. A macro could be something like:
@inherits Umbraco.Web.Macros.PartialViewMacroPage
@using Umbraco.Core.Models
@using Umbraco.Web
@using System.Linq;
@{ Layout = null;
Response.ContentType = "text/xml";
}<?xml version='1.0' encoding='UTF-8' ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemalocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
@ListChildNodes(Model.Content.AncestorOrSelf(1))
</urlset>
@helper ListChildNodes(IPublishedContent startNode)
{
const int maxLevelForSiteMap = 100;
foreach (var node in startNode.Children.Where(x => Umbraco.MemberHasAccess(x.Id, x.Path)).Where(x => !Umbraco.IsProtected(x.Id, x.Path)).Where(x => x.IsVisible()))
{
if (node.TemplateId > 0)
{
<url>
<loc>@node.UrlWithDomain()</loc>
<lastmod>@(string.Format("{0:s}+00:00", node.UpdateDate))</lastmod>
@{
var freq = node.GetPropertyValue<string>("SearchEngineSitemapChangeFreq");
var pri = node.GetPropertyValue<string>("SearchEngineSitemapPriority");
}
@if (!string.IsNullOrEmpty(freq))
{
<changefreq>@freq</changefreq>
}
@if (!string.IsNullOrEmpty(pri))
{
<priority>@pri</priority>
}
</url>
}
if (node.Level <= maxLevelForSiteMap && node.Children.Any())
{
@ListChildNodes(node)
}
}
}
You need to have properties for those items inside each document which should have them and will be displayed / indexed etc. You can achieve the goal with couple ways:
You can create a small composition document type with all required properties (SeoTitle, SeoDescription, SeoKeywords and whatever you want more) and attach it to your document types (maybe even some master document type used for all webpages rendered and indexed across your site). Then you can create a partial view or render the values from it on the master template. In my opinion this way is giving you the best control over what's there and we're doing it for all of our projects (just importing exported composition doctype and attaching it to desired items in specific solution).
You can use a package e.g. https://our.umbraco.org/projects/backoffice-extensions/seo-metadata-for-umbraco/ which is doing exactly the same thing in a little bit different way, gives you the possibility to retrieve those properties from CurrentPage object. You can read more about the package here: https://ryanl.me/2015/04/13/seo-metadata-for-umbraco/.
Here's how my company is doing it and how they taught me and it is working up until this very moment we are talking.
On your Settings, create a document type without a template and call it SEO
. Then add the properties you want for your SEO there (and of course you can update them at any time). After that, create your Home Page and every other node as a child to the SEO tab and make them inherit the properties. That way in every page you create, the SEO properties will be inherited.
Pro tip I learned recently. You can use GetPropertyValue for the SEO properties in each node as if it was the node's properties to begin with. (No need to search Ancestors
or Descendants
etc.
Here's how my Settings tab looks like:
Preview