Search code examples
meta-tagsblazorblazor-server-side

Setting html meta tags with Blazor


This is in reference to #10450.

Goal: Set meta tags (title, description etc) for SEO and Open Graph purposes with data coming from the page itself. Using the Javascript interop won't help as pages won't be able to be crawled.

I have used a suggestion by @honkmother and moved the base component further up the tree to encapsulate the <html> tag but for some reason this has affected routing. All links are prepended with ~/ and I can't seem to understand why.

I have created an example repo here if anyone is intersted in taking a look.

_Hosts.cshtml

@page "/"
@namespace BlazorMetaTags.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<component type="typeof(AppBase)" render-mode="ServerPrerendered" />

AppBase.cs

using BlazorMetaTags.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace BlazorMetaTags
{
    public class AppBase : ComponentBase
    {
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            builder.OpenElement(0, "html");
            builder.AddAttribute(1, "lang", "en");

            builder.OpenElement(2, "head");
            builder.OpenComponent<Head>(3);
            builder.CloseComponent();
            builder.CloseElement();

            builder.OpenElement(3, "body");

            builder.OpenElement(4, "app");
            builder.OpenComponent<App>(5);
            builder.CloseComponent();
            builder.CloseElement();

            builder.OpenComponent<Body>(6);
            builder.CloseComponent();

            builder.AddMarkupContent(7, " <script src='_framework/blazor.server.js'></script>");
            builder.CloseElement();
            builder.CloseElement();

        }

    }

    public class MetaTags
    {
        public string Title { get; set; } = "";

        public string Description { get; set; } = "";
    }
}

Head.razor component to set the meta tags

@inject AppState _appState

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

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

    <base href="~/" />
    <link rel="stylesheet" href="/css/bootstrap/bootstrap.min.css" />
    <link href="/css/site.css" rel="stylesheet" />
</head>

@code {

    protected override async Task OnInitializedAsync()
    {
        _appState.OnChange += StateHasChanged;
    }

}

Body.razor

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

AppState.cs

using System;

namespace BlazorMetaTags
{
    public class AppState
    {
        public MetaTags MetaTags { get; private set; } = new MetaTags();

        public event Action OnChange;

        public void SetMetaTags(MetaTags metatags)
        {
            MetaTags = metatags;
            NotifyStateChanged();
        }

        private void NotifyStateChanged() => OnChange?.Invoke();
    }
}

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddScoped<AppState>();
    services.AddSingleton<WeatherForecastService>();
}

Solution

  • As suggested by Meisam Dehghan in a comment, the correct solution is to change

    <base href="~/" />
    

    to

    <base href="/" />
    

    This worked for me!