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.
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.
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.
Blazor Server is a SPA framework that renders on the server and thus, this approach will not work.
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.
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.
MetadataProvider.cs
.MetadataTransferService.cs
.Startup.cs
.Metadata.razor
component.MetadataTransferService
and MetadataProvider
to initialize the metadata.Metadata.razor
in _Host.cshtml
page.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.
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; } }
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>