
Blazor WASM Controlling Head Content
Author - Abdul Rahman (Bhai)
Blazor
34 Articles
Table of Contents
What we gonna do?
In this article, let's learn about how to control head content like page title, meta tags and other script tags in Blazor WASM application.
Note: If you have not done so already, I recommend you read the article on Blazor WASM Forms Validation.
Prior to .NET 6.0 there is no straight forward way to control head content in Blazor WASM application. We need to do some javascript hacks to achieve this. With .NET 6.0, we got a new, easy and simple way to control head content. Now Razor components can modify the HTML <head> element content of a page, including setting the page's title (<title> element) and modifying metadata (<meta> elements).
Why we gonna do?
With Blazor WASM the responsibility of rendering HTML is on the client side and this brings a new challenge Search Engine Optimization (SEO) and Social Media Optimization (SMO). Search engine bots crawl our sites to list it in the search engine results while Social media bots crawl our sites when we share them on social media which makes the sharing link to look good with eye catching posters or with pages body content.
Ofcourse the (<body>) content of our site is more important but (<head>) content gives extra control with <meta> tags for SEO and SMO optimization.
How we gonna do?
Steps
- Add HeadOutlet Component - builder.RootComponents.Add<HeadOutlet>("head::after"); in Program.cs.
- Add PageTitle Component - <PageTitle>Your Page Title</PageTitle> inside your component or page.
- Add HeadContent Component - <HeadContent><meta name="description" content="description"></HeadContent> inside your component or page.
HeadOutlet Component
This is fairly a straight forward step and this will mostly be available and configured already in Program.cs in new Blazor WASM projects. If not all you need to do is to add the following line builder.RootComponents.Add<HeadOutlet>("head::after"); in Program.cs.
This component takes the responsibilty of appending the content of PageTitle and HeadContent to (<head>) tag using ::after pseudo element.
PageTitle Component
The PageTitle component is used to control page <title> when rendering HTML to a HeadOutlet component. This can be placed anywhere inside component or page in your blazor application.
<PageTitle>@Title</PageTitle>
<p>
<label>
Try giving new <code>PageTitle</code>.
<input @bind-value="Title" @bind-value:event="oninput" />
</label>
</p>
@code {
private string Title = "Blazor WASM Controlling Head Content";
}
HeadContent Component
The HeadContent component is similar to PageTitle component but used to append its content to the <head> when rendering HTML to a HeadOutlet component. We can also give <title>inside this component. This can be placed anywhere inside component or page in your blazor application.
<HeadContent>
<meta name="Head Content Demo" content="@description">
</HeadContent>
@code {
private string description = "Head Content Demo";
}
Drawbacks
Browsers are more forgiving that sometimes they ignore our mistakes. Here is why?
What if we use PageTitle twice?
@page "/pagetitledemo"
<PageTitle>Page Title 1</PageTitle>
<PageTitle>Page Title 2</PageTitle>
Blazor will not create two title tags inside of the head of your document. Instead, the last PageTitle component to be rendered will be reflected as the title of your document.
What if we give <title> inside HeadContent?
<PageTitle>Page title from PageTitle</PageTitle>
<HeadContent>
<title>Page title from HeadContent</title>
</HeadContent>
Browser will use second title. But I recommend using PageTitle component for titles as it designed for that and it takes care of creating and updating.
What if we have mutiple HeadContent?
@page "/headcontentdemo"
<HeadContent>
<meta name="description" content="page description" />
<meta name="author" content="Abdul Rahman - I ❤️ .NET">
</HeadContent>
<HeadContent>
<meta name="description" content="another page description" />
</HeadContent>
Blazor will not merge instead it will replace the contents of last HeadContent and remaining will be lost.
Alternatives
We can overcome the above drawbacks with Toolbelt.Blazor.HeadElement. This is an awesome Nuget Package which covers some edge cases that are not covered by PageTitle and HeadContent components.
Realworld example
Here is I ❤️ DotNet's HeadContent Configuration - A good Real world example.
@inject IConfiguration configuration
@inject Sitemaps sitemap
<Title>@Title - I ❤️ DotNet</Title>
<Meta Property="description" Content="@Description" />
<Meta Property="keywords" Content="@($".NET, DotNet, I Love DotNet, I ❤️ DotNet, Blazor, Blazor (WebAssembly), Blazor Wasm, C#, Entity Framework, LINQ, ML.NET, Web API, TDD, OOPS, Middleware, JWT, Dependency Injection, {string.Join(", ", Keywords)}")" />
<Meta Property="url" Content="@($"{BaseUrl}{ContentType}/{Slug}/")" />
<Meta Property="identifier-URL" Content="@($"{BaseUrl}{ContentType}/{Slug}/")" />
<Meta Property="og:site_name" Content="I Love DotNet" />
<Meta Property="og:type" Content="article" />
<Meta Property="og:title" Content="@Title" />
<Meta Property="og:image" Content="@($"{BaseUrl}image/brand/mini-logo.png")" />
<Meta Property="og:image" Content="@($"{BaseUrl}image/{normalizedPosterPath}/{Slug}.png")" />
<Meta Property="og:image:alt" Content="DotNet bot with heart and DotNet brand" />
<Meta Property="og:description" Content="@Description" />
<Meta Property="og:url" Content="@($"{BaseUrl}{ContentType}/{Slug}/")" />
<Meta Property="twitter:card" Content="summary_large_image" />
<Meta Property="twitter:title" Content="@Title" />
<Meta Property="twitter:image" Content="@($"{BaseUrl}image/brand/mini-logo.png")" />
<Meta Property="twitter:image" Content="@($"{BaseUrl}image/{normalizedPosterPath}/{Slug}.png")" />
<Meta Property="twitter:image:alt" Content="DotNet bot with heart and DotNet brand" />
<Meta Property="twitter:description" Content="@Description" />
<Meta Property="twitter:url" Content="@($"{BaseUrl}{ContentType}/{Slug}/")" />
<Meta Property="article:published_time" Content="@CreatedOn.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")" />
<Meta Property="article:modified_time" Content="@ModifiedOn.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")" />
<Meta Property="article:tag" Content=".NET" />
<Meta Property="article:tag" Content="DotNet" />
<Meta Property="article:tag" Content="I Love DotNet" />
<Meta Property="article:tag" Content="I ❤️ DotNet" />
<Meta Property="article:tag" Content="Blazor" />
<Meta Property="article:tag" Content="Blazor (WebAssembly)" />
<Meta Property="article:tag" Content="Blazor Wasm" />
<Meta Property="article:tag" Content="C#" />
<Meta Property="article:tag" Content="Entity Framework" />
<Meta Property="article:tag" Content="LINQ" />
<Meta Property="article:tag" Content="ML.NET" />
@foreach(var keyword in Keywords)
{
<Meta Property="article:tag" Content="@keyword" />
}
@foreach(var sitemap in sitemap.Files)
{
<Link Rel="sitemap" Href="@($"{BaseUrl}{sitemap}")" type="application/xml" title="Sitemap" />
}
<Link Rel="alternate" Href="@($"{BaseUrl}atom.xml")" type="application/rss+xml" />
<Link Rel="alternate" Href="@($"{BaseUrl}{ContentType}/{Slug}/")" hreflang="en" />
<Link Rel="alternate" Href="@($"{BaseUrl}{ContentType}/{Slug}/")" hreflang="x-default" />
<Link Rel="canonical" Href="@($"{BaseUrl}{ContentType}/{Slug}/")" />
<Link Rel="index" Href="@($"{BaseUrl}{ContentType}/{Slug}/")" title="@($"{Title} - I ❤️ DotNet")" />
<HeadContent>
@(new MarkupString(
$$""""
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"publisher": {
"@type": "Organization",
"name": "I Love DotNet",
"url": "{{BaseUrl}}",
"logo": {
"@type": "ImageObject",
"url": "{{BaseUrl}}favicon.ico",
"width": 16,
"height": 16
}
},
"author": {
"@type": "Person",
"name": "Abdul Rahman Shabeek Mohamed",
"image": {
"@type": "ImageObject",
"url": "//https://www.gravatar.com/avatar/6f221209a03c1800679fc684dea24bf2?s=250&d=mm&r=x",
"width": 250,
"height": 250
},
"url": "{{BaseUrl}}author/abdulrahman/",
"sameAs": [
"https://www.linkedin.com/in/fingers10",
"https://github.com/fingers10",
"https://stackoverflow.com/users/10851213/fingers10",
"https://www.youtube.com/channel/UCOS3wCw7SVXjXXffMPqid7A"
]
},
"headline": "{{Title}}",
"url": "{{BaseUrl}}{{ContentType}}/{{Slug}}/",
"datePublished": "{{CreatedOn:yyyy-MM-ddTHH:mm:ss.fffZ}}",
"dateModified": "{{ModifiedOn:yyyy-MM-ddTHH:mm:ss.fffZ}}",
"image": {
"@type": "ImageObject",
"url": "{{BaseUrl}}image/{{normalizedPosterPath}}/{{Slug}}.png",
"width": 1265,
"height": 827
},
"keywords": ".NET, DotNet, I Love DotNet, I ❤️ DotNet, Blazor, Blazor (WebAssembly), Blazor Wasm, C#, Entity Framework, LINQ, ML.NET, Web API, TDD, OOPS, Middleware, JWT, Dependency Injection, {{string.Join(", ", Keywords)}}",
"description": "{{Description}}",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{BaseUrl}}"
}
}
</script>
""""
))
</HeadContent>
@code {
private string BaseUrl => configuration.GetValue<string>("baseUrl")!;
private string normalizedPosterPath => PosterPath.ToLower();
[Parameter, EditorRequired] public string Title { get; set; } = default!;
[Parameter, EditorRequired] public string Description { get; set; } = default!;
[Parameter, EditorRequired] public DateTime CreatedOn { get; set; }
[Parameter, EditorRequired] public DateTime ModifiedOn { get; set; }
[Parameter, EditorRequired] public string Slug { get; set; } = default!;
[Parameter, EditorRequired] public string PosterPath { get; set; } = default!;
[Parameter, EditorRequired] public string ContentType { get; set; } = default!;
[Parameter, EditorRequired] public List<string> Keywords { get; set; } = new();
}
Summary
In this article, we learned about what is HeadOutlet, HeadContent and PageTitle and how to control <head> content in HTML in blazor WASM application. Make sure to use this for dynamic head contents. For static head contents it is better to add them directly in index.html.