Get Your Head Together With Blazor’s New HeadContent and PageTitle

Niels Swimberghe

Niels Swimberghe - - .NET

Follow me on Twitter, buy me a coffee

This blog post was written for Telerik and originally published at Telerik's blog here.

Let’s take a look at the three Blazor components .NET 6 is introducing to help you manage the head of your document—PageTitle, HeadContent, and HeadOutlet.

With the rise of JavaScript frameworks and the web trending toward Single Page Applications (SPAs), a lot of tasks got easier and other tasks harder. One of the fundamental challenges that has arisen from moving the responsibility of rendering HTML in the browser and away from the server, is Search Engine Optimization (SEO) and Social Media Optimization (SMO).

Search engine bots crawl the content on your website so the search engine can index and return it as part of the Search Engine Result Pages (SERP). On the other hand, social media bots crawl particular pages when they are shared to the social media site to render a good-looking and engaging card.

But when those bots crawl a SPA without JavaScript enabled, all they see is an empty page. This means you won’t find that page in any SERP nor see a nice card on social media.

Note: Some bots do have JavaScript enabled, but it takes longer for those bots to crawl content. You should make sure your content is rendered with or without JavaScript to maximize your SEO and SMO.

Luckily, with Blazor you don’t have to choose between rendering HTML on the server or client. Blazor can do both! You can use Blazor WebAssembly or Blazor Server. In both flavors of Blazor, you can use pre-rendering.

To optimize for SEO and SMO, your <body> content is the most important. But to further improve and take control of how your content appears, you need to add metadata to your <head>. The challenge in doing this with Blazor is that your application is running inside of the body of the document. To update the head of the document, you would have to create a homebrew solution combining server-side Razor and client-side JavaScript interop.

But in the upcoming release of .NET 6, we are blessed with two new goodies to get our head in order—the PageTitle and HeadContent component.

The PageTitle Component

The new PageTitle component is a Blazor component that you can put anywhere inside of your Blazor application. Even though the Blazor application is being rendered in the body of the document, Blazor will take care of updating the title tag in the head when you use the new PageTitle component.

You will find PageTitle components in the Blazor pages that come with the .NET 6 version of the Blazor Wasm template. Take Pages/Counter.razor for example:

@page "/counter"



At line 3, you can see how the PageTitle component can be used. Simply add your desired title inside of the component, and when you navigate to the page at /counter, you will see the title changing to “Counter”.

Note: The PageTitle component is inside of the Microsoft.AspNetCore.Components.Web namespace.

Now that you can change the title from Blazor, you can also use Razor’s rendering engine to dynamically change it.

If you change the code like below, the currentCount field will increment when the user clicks on the “Click me” button. The title of the page will also automatically update when currentCount is incremented.

@page "/counter"

<PageTitle>Current count: @currentCount</PageTitle>


<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="() => currentCount++">Click me</button>

@code {
  private int currentCount = 0;

You don’t necessarily need to put the PageTitle inside of your page components. You can also put the PageTitle component inside of other Blazor components, but that may make it harder to keep track of which component is changing the title.

Which brings us to the following question: What happens when there are multiple PageTitle components rendered on the same page?

@page "/counter"

<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. In this case that would be “Page Title 2”.

Note: As part of .NET 6 Release Candidate 1, there is a bug that causes the title not to update correctly on page load, but it does when you navigate away and back to the page. This should be resolved when .NET 6 is released.

The HeadContent Component

The new HeadContent component is similar to the PageTitle component. But instead of setting the title of the document, the content of the component will be moved to the head of your document.

@page "/counter"

  <meta name="description" content="Use this page to count things!" />
  <meta name="author" content="Niels Swimberghe">
  <link rel="icon" href="favicon.ico" type="image/x-icon">

In the example above, the following document head will be produced:

<!-- Existing head content omitted for brevity -->
  <meta name="description" content="Use this page to count things!">
  <meta name="author" content="Niels Swimberghe">
  <link rel="icon" href="favicon.ico" type="image/x-icon">

You could also use the HeadContent component to set the title of the document, but HeadContent will simply append the title tag without looking for any existing title to update.

You could accidentally create multiple page titles which is invalid HTML. This example will add two title tags in the head:

<PageTitle>Page title from PageTitle</PageTitle>
  <title>Page title from HeadContent</title>

The resulting head will look like this:

  <!-- Existing head content omitted for brevity -->
  <title>Page title from PageTitle</title>
  <title>Page title from HeadContent</title>

Browsers are generally forgiving and will use the second title, but you should just stick to using the PageTitle component, which takes care of creating or updating the title of the document.

So what happens when you render multiple HeadContent components?

@page "/counter"

  <meta name="description" content="Use this page to count things!" />
  <meta name="author" content="Niels Swimberghe">
  <link rel="icon" href="favicon.ico" type="image/x-icon">
  <meta name="description" content="This is the description from the second HeadContent" />

Blazor will not merge the content of the two HeadContent components, but instead will use the content that is rendered last. This means in the example above, the content of the second HeadContent component will be put into the head of the document, and the content in the first HeadContent component is lost.

Due to this behavior, it is best to keep the PageTitle and PageContent components in your Blazor page components instead of descendant components.

The HeadOutlet Component

There is a third component that hasn’t been mentioned yet. The HeadOutlet component is the component responsible for outputting the title from the PageTitle component and the content from the HeadContent component.

If you’re using the updated Blazor Wasm template from .NET 6, you’re already using this component because it is pre-configured for you in the Program.cs file.

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using YourProjectName;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

At line 7, the HeadOutlet component is being added as a root component.


In CSS, ::after creates a pseudo-element as the last child of the selected nodes, and in this case, it means Blazor will add the HeadOutlet component as the last child of the head node.

This is the default for the Blazor Wasm template, which renders the Blazor app and HeadOutlet on the client and also depends on JavaScript. As discussed earlier, many search engine bots and social media bots will not execute your JavaScript. As a result, the bots will not see the result of your client-side App or HeadOutlet component.

It will take some extra work to modify your Blazor Wasm application, but you can follow this guide from the ASP.NET Docs to prerender your app: Prerender and integrate ASP.NET Core Razor components.

After following the guide to prerender your app, you will use the HeadOutlet component like this instead:

<component type="typeof(HeadOutlet)" render-mode="WebAssemblyPrerendered" />

If you’re using the Blazor Server template, you already have everything set up to prerender and the HeadOutlet component will look like this:

<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />

What Should You Put in Your Head?

The content of the head of your document depends on the requirements of the application you are building. If you’re building any website or application where content and SEO is important, then this metadata is extra relevant:

Here’s an example of how you could use the PageTitle and HeadContent component to add the previously mentioned metadata to your head:

@page "/good-content"
@inject NavigationManager NavigationManager

<PageTitle>Title of some good content</PageTitle>
  <meta name="description" content="A small description of some good content on this page" />
  <link rel="sitemap" type="application/xml" title="Sitemap" href="@(NavigationManager.BaseUri)sitemap.xml" />
  <link rel="alternate" type="application/rss+xml" href="@(NavigationManager.BaseUri)atom.xml">
  <link rel="canonical" href="@(NavigationManager.BaseUri)good-content" />
  <meta name="robots" content="index, follow" />
  <script type="application/ld+json">
    "@@context": "https://schema.org/",
    "@@type": "Recipe",
    "name": "Party Coffee Cake",
    "author": {
      "@@type": "Person",
      "name": "Mary Stone"
    "datePublished": "2018-03-10",
    "description": "This coffee cake is awesome and perfect for parties.",
    "prepTime": "PT20M"

Take note of the usage of the NavigationManager to dynamically construct the URLs.

If you want to make sure your content looks good when sharing on social media, you want to add the open-graph and Twitter metadata like this example:

  <meta property="og:title" content="Title of some good content" />
  <meta property="og:description" content="A small description of some good content on this page" />
  <meta property="og:image" content="@(NavigationManager.BaseUri)page/to/open-graph-image.png" />
  <meta property="og:image:alt" content="Alternative description for your image" />
  <meta property="og:type" content="website" />
  <meta property="og:url" content="@(NavigationManager.BaseUri)good-content" />
  <meta property="og:site_name" content="The name of your site" />
  <meta name="twitter:title" content="Title of some good content" />
  <meta name="twitter:description" content="A small description of some good content on this page" />
  <meta name="twitter:image" content="@(NavigationManager.BaseUri)page/to/twitter-image.png" />
  <meta name="twitter:image:alt" content="Alternative description for your image" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:site" content="@@YourTwitterHandle" />
  <meta name="twitter:creator" content="@@YourTwitterHandle" />

The list of suggested tags for both scenarios are non-conclusive. There’s an open-source project which publishes a good list of tags you should consider adding to your head.

Keep in mind that you don’t need to use the HeadContent component for tags in your head that are constant. Simply add those tags to the head in your index.html or _Layout.cshtml file depending on your project.


.NET 6 is introducing three Blazor components to help you manage the head of your document.

  1. Using the PageTitle component, you can dynamically set and update the title of your document.
  2. The HeadContent component lets you dynamically add content to the head of your document.
  3. The HeadOutlet component is responsible for taking the content from the PageTitle and HeadContent component and putting it in the head of your document.

You can use these components in Blazor Wasm and Blazor Server. You can also prerender these new components to optimize for SEO and SMO.

Related Posts

Related Posts

Picture of Niels—white male with long blond hair— with a shocked expression

Guest on .NET Docs Show: Making Phone Calls 📞 from Blazor WebAssembly with Twilio Voice

- .NET
Earlier this week, the folks at the .NET Docs Show invited me over to talk about Twilio, .NET, and Blazor WebAssembly. We discussed different architectures, workflow diagrams, Twilio capabilities, and how to integrate them using ASP.NET WebAPI's and Blazor WebAssembly.
Blazor logo

Pre-render Blazor WebAssembly at build time to optimize for search engines

- .NET
Using pre-rendering tools like react-snap, you can pre-render Blazor WASM. Additionally, you can integrate these pre-rendering tools inside of your continuous integration and continuous deployment pipelines.
How to deploy ASP.NET Blazor WebAssembly to Azure Static Web Apps. Blazor logo pointing to a GitHub logo pointing to an Azure logo.

How to deploy Blazor WASM & Azure Functions to Azure Static Web Apps using GitHub

- .NET
With ASP.NET Blazor WebAssembly you can create .NET applications that run inside of the browser . The output of a Blazor WASM project are all static files. You can deploy these applications to static site hosts, such as Azure Static Web Apps and GitHub Pages.
ASP.NET Blazor Logo

How to run code after Blazor component has rendered

- .NET
Blazor components render their template whenever state has changed and sometimes you need to invoke some code after rendering has completed. This blog post will show you how to run code after your Blazor component has rendered, on every render or as needed.
Blazor logo next to JavaScript logo

Communicating between .NET and JavaScript in Blazor with in-browser samples

- .NET
The success of Blazor relies heavily upon how well it can integrate with the existing rich JavaScript ecosystem. The way Blazor allows you to integrate, is by enabling you to call JavaScript functions from Blazor and .NET functions from JavaScript also referred to as 'JavaScript interoperability'.