Search Engine Optimization (SEO)

SEO plays a crucial part in any website, especially for SPA. In this guide, when referencing SEO, we mean the technical part and not the non-technical part. We only focus on how to provide your website information to search engines.

You can download the example code used in this topic on GitHub.

Prerequisites

This guide assumes that you have a basic understanding of the following.

The technical part of SEO

The goal of the technical part is to change the metadata tags in the <head> tag based on the request URL. For example, the <title> tag changes when the user navigates to a new component.

Blazor Server does support SEO. However, the default template for Blazor Server does not support SEO and you have to implement it yourself.

Common mistakes when SEO with Blazor Server

Here are some common mistakes you need to avoid when implementing SEO for Blazor Server. Some of them work in the development stage but in production stage it does not work.

  1. Use JS to change the metadata tags.

Blazor Server is a SPA framework that renders on the server and thus, this approach will not work.

  1. Try to update the rendered HTML tag.

Blazor Server is rendered on top of Razor Pages, the Razor Pages is not a SPA framework therefore it does not support updating the rendered HTML tag.


Implementing SEO for Blazor Server

The following steps are the steps to implement SEO for Blazor Server. These steps use the native Blazor Server technique and no third party is required.

  1. Create a service MetadataProvider.cs.
  2. Create a data transfer service MetadataTransferService.cs.
  3. Add both services as scoped at Startup.cs.
  4. Create a Metadata.razor component.
  5. Use the MetadataTransferService and MetadataProvider to initialize the metadata.
  6. Use Metadata.razor in _Host.cshtml page.

Creating the services

We are going to create the MetadataProvider and MetadataTransferService service. As the name suggested, the MetadataProvider will provider the route and its metadata details. You can store the data in the database and use this class to get the metadata along the route. This is an example of our MetadataProvider implementation.

public class MetadataProvider
{
    public Dictionary<string, MetadataValue> RouteDetailMapping { get; set; } = new()
    {
        {
            "/",
            new()
            {
                Title = "Blazor School Example",
                Description = "Visit more at https://blazorschool.com"
            }
        },
        {
            "/about",
            new()
            {
                Title = "About us",
                Description = "Email us: dotnetprotech@gmail.com - The DotNetPro team."
            }
        }
    };
}

The next step is to create the MetadataTransferService service. As the name suggested, the MetadataTransferService will act as a data transferer between components. See Component Interaction for more information about the transfer service.

public class MetadataTransferService : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _title;

    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged();
        }
    }

    private string _description;
    private readonly NavigationManager _navigationManager;
    private readonly MetadataProvider _metadataProvider;

    public string Description
    {
        get => _description;
        set
        {
            _description = value;
            OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new(propertyName));
    }

    public MetadataTransferService(NavigationManager navigationManager, MetadataProvider metadataProvider)
    {
        _navigationManager = navigationManager;
        _metadataProvider = metadataProvider;
    }

    public void Dispose()
    {
        
    }
}

You need 2 interfaces INotifyPropertyChanged and IDisposable for the transfer service. The INotifyPropertyChanged is to treat every property change equally, you can declare one event for each property also, this interface is not required. The IDisposable is also not required but it is recommended to have it because you are going to subscribe to the LocationChanged event from the NavigationManager and you need to unsubscribe to that event using the Dispose method. Add the UpdateMetadata method to update the metadata.

private void UpdateMetadata(string url)
{
    var metadataValue = _metadataProvider.RouteDetailMapping.FirstOrDefault(vp => url.EndsWith(vp.Key)).Value;

    if (metadataValue is null)
    {
        metadataValue = new()
        {
            Title = "Default",
            Description = "Default"
        };
    }

    Title = metadataValue.Title;
    Description = metadataValue.Description;
}

Subscribe to the LocationChanged event.

public MetadataTransferService(NavigationManager navigationManager, MetadataProvider metadataProvider)
{
    ...
    _navigationManager.LocationChanged += UpdateMetadata;
}

private void UpdateMetadata(object sender, LocationChangedEventArgs e)
{
    UpdateMetadata(e.Location);
}

public void Dispose()
{
    _navigationManager.LocationChanged -= UpdateMetadata;
}

Whenever users navigate to a new page, the LocationChanged event is fired and the metadata is refreshed. The LocationChanged event will not fire when the user enters the first page because the initial location is not changed. To allow crawlers and users to see the metadata for the first time enter a page, add a call to UpdateMetadata method.

public MetadataTransferService(NavigationManager navigationManager, MetadataProvider metadataProvider)
{
    ...
    UpdateMetadata(_navigationManager.Uri);
}

Register both of the services at the Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<MetadataProvider>();
    services.AddScoped<MetadataTransferService>();
}
The MetadataProvider can also be a scoped service. You should choose which is the best fit in your case and not always make it as singleton as the example.

Creating the metadata component

The Metadata.razor includes all the metadata tags you want on the website. Some basic tags that every website must have are the <title> and <meta name="description" />. The Metadata.razor will use the data from the transfer service to render the HTML elements. This is an example of our Metadata.razor implementation.

@inject MetadataTransferService MetadataTransferService
@implements IDisposable

<title>@MetadataTransferService.Title</title>
<meta name="description" content="@MetadataTransferService.Description" />

@code {
    protected override void OnInitialized()
    {
        MetadataTransferService.PropertyChanged += OnMetadataChanged;
    }

    private void OnMetadataChanged(object sender, PropertyChangedEventArgs e)
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        MetadataTransferService.PropertyChanged -= OnMetadataChanged;
    }
}

Using the metadata component

Once you have the Metadata.razor, you need to use it in the _Host.cshtml. Keep in mind that _Host.cshtml is a Razor Page and your Metadata.razor is a Razor Component and to use a Razor Component inside a Razor Page is a bit different than using a Razor Component inside another Razor Component. Remove the existing <title> tag in the _Host.cshtml.

<head>
    ...
    <component type="typeof(Metadata)" render-mode="ServerPrerendered"/>
</head>
BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2025 Blazor School
An unhandled error has occurred. Reload 🗙