Skip to content

Html meta tags, page title #16018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
GoranHalvarsson opened this issue May 16, 2018 · 19 comments
Closed

Html meta tags, page title #16018

GoranHalvarsson opened this issue May 16, 2018 · 19 comments
Labels
area-blazor Includes: Blazor, Razor Components

Comments

@GoranHalvarsson
Copy link

Hello people

You guys are doing a wonderful job with Blazor :-)

I have a question regarding Html meta tags. The app itself will be placed in the app element on the page. But how about stuff in the header? Like meta tags and so on.
Let say I would like to change/update meta tag data depending on what page (in the app). Or just update the page title.
How to do this? By Javascript interop?

@Andrzej-W
Copy link

Good question. I was thinking about this problem in my own application and so far I have no solution. Think about server side rendering in the future. Correct meta tags are essential for SEO, for integration with Facebook/Twitter, etc.

I hope that we will be able to create multilingual Blazor applications in the future. To do it correctly we have to handle lang attribute in html element.

We have to be able to modify main index.html file from Blazor application and selected solution have to be compatible with server side rendering.

@GoranHalvarsson
Copy link
Author

Yes exactly. My vision/hope is that Blazor will be "the new way" of doing web pages. Like replacing the "old way" of doing server-rendered pages. If we(you) could find a way how to handle stuff outside the app element, like Meta tags, title etc. Then the game is afoot :-)

@Andrzej-W
Copy link

Andrzej-W commented May 21, 2018

I have found yet another example why we need a way to modify HTML page elements outside of our Blazor application tag. We can have an application with multiple pages and someone can add a link to Favourites in the web browser. Link should have proper title - otherwise links to different pages are indistinguishable.

@SteveSandersonMS
Copy link
Member

For most nontrivial Blazor apps, you'll want some notion of a data store that issues notifications when its state changes. There's an example of this in the FlightFinder sample: https://github.com/aspnet/samples/tree/master/samples/aspnetcore/blazor/FlightFinder

With this kind of architecture, you can have "page title" as one of the data items tracked in the store, and can subscribe to that in your layout to change the <title> element's contents whenever that changes.

@DNF-SaS
Copy link

DNF-SaS commented May 22, 2018

@SteveSandersonMS How do you change the content of the <title> element located in in index.html? Using JS-Interop (document / page.title, I suppose?)

@SteveSandersonMS
Copy link
Member

@DNF-SaS Yes

@Andrzej-W
Copy link

Andrzej-W commented May 22, 2018

@SteveSandersonMS Thank you for suggestion. Of course we can use data store and modify what we want in JavaScript (title, metatags, etc.). But in the future we will have server side rendering. It would be nice to find a solution which will work on the client and on the server.

In ordinary ASP.NET Core application I have a base ViewModel for all my pages. I use this base class in _Layout.cshtml like this:

@model ViewModelBase
<!DOCTYPE html>
<html lang="@Model.Language" prefix="og: http://ogp.me/ns#">
<head>
<title>@Model.Title</title>
@if (!string.IsNullOrEmpty(Model.OgVideo))
{
    <meta property="og:video" content="@Model.OgVideo" />
}

As you can see I assume that some model properties (Language, Title) are always available and I render them unconditionally. On the other hand not every page has video which can be shared on social sites and og:video metatag is rendered conditionally. It would be nice to have similar experience on the client. Maybe it is possible to add some event to the router (for example OnHeadRender). In that event we should be able to render at least the opening <html> tag and <head></head>. This event probably should be executed after OnParametersSet - page component should have a chance to set some state in global data store (similar in concept to my base ViewModel in ASP.NET Core application).

I understand that it is not a high priority task at this stage of Blazor development, but I think it should be tracked somewhere.

@raphadesa
Copy link

If would be great if you had a namespace (or helpers) to manage header's metadata (such as title, meta keywords), and also a set of helpers to manage Cookies, and also a wrapper to the windows javascript namepace !

@topnguyen
Copy link

@Andrew-MSFT SAP always face the issue meta tags and title with the SEO :( haiz.. so.. Blazor has nothing diff about that, right?

@danroth27
Copy link
Member

@topnguyen Correct, the story is pretty much the same.

@evrenunal
Copy link

I have similar problem for seo purposes,
I need to be able to modifiy title and description tags for every "page"

@plasticalligator
Copy link

I used a bunch of ugly JS-Interop and pre-rendering pages as static HTML to get crawlers working around; but it's definitely not ideal.

It would be really great to see a "MetaHelper" built into Blazor akin to UriHelper where I can just call MetaHelper.Links["en"].href = "XYZ"; or MetaHelper.Title = "XYZ";

@chrdlx
Copy link

chrdlx commented May 8, 2019

Is there an official recommended way to solve this yet?
The only way possible right now seems to build the whole site with .cshtml + .razor pairs, for example:

Home.cshtml (which renders the home component)
Home.Razor
Products.cshtml (which renders the products component)
Products.razor
etc...etc...

This way you create a hybrid approach, in which everything in the page is interactive (without postbacks), except links to other pages which would trigger the browser redirect to another .cshtml razor page, each one with its custom head tags.

Is there a known better solution?

Also even this solution doesn't work since <a href="etc"> link tags seem to be catched by the framework blocking the browser to properly redirect.
(issue which is being tracked here #9834 )

Regards!

@SteveSandersonMS
Copy link
Member

Currently the simplest and cleanest solution is to use JS interop to modify the document state.

We are planning to improve this, though we don't have a final design or ETA yet.

@plasticalligator
Copy link

plasticalligator commented May 8, 2019

JS Interop? What? No!

You don't even need anything sophisticated to do this.
This is terrible code made one late night in 0.9 but it should give you an idea on how to do this; you can probably do this more way efficiently.

Basically, create an html component, a body component, and a head component; render the html component in Index.cshtml; put a reference to the Head Component inside a Metatag Container; set the reference to "this" from the Head component upon init, and also pass this container into the Body component and whatever is inside of the body.

From there you can update the Head component from within the body, which will update the SEO tags as expected as long as you invoke StateHasChanged on the Head blazor component. This works with pre-rendering, allowing you to be appropriately crawled.

Here's what my Index.cshtml looks like

@page "{*clientPath}"
@using dao.Server
<!DOCTYPE html>
<html>

@(await Html.RenderComponentAsync<html>())

</html>

Then I have two classes called "html" and "metatags".
The html class creates the html tags, and I just store the the header (script tags, etc) inside of a html file that I inject as markup.

    public class html : ComponentBase
    {
        public static string HeadTXT = File.ReadAllText("head.htm");
        Metatags meta = new Metatags() { Title = "some title" };
        protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
        {

            builder.OpenElement(0, "head");
            builder.AddMarkupContent(1, HeadTXT);
            builder.OpenComponent<Head>(2);
            builder.AddAttribute(3, "MetaInfo", meta);
            builder.CloseComponent();
            builder.CloseElement();

            builder.OpenElement(4, "body");
            builder.OpenComponent<Body>(5);
            builder.AddAttribute(6, "MetaInfo", meta);
            builder.CloseComponent();
            builder.AddMarkupContent(7, "<script src=\"_framework/components.server.js\"></script>");
            builder.CloseElement();

        }
    }

    public class Metatags
    {
        public string Title { get; set; } = "example.nyc — weirdest hack ever";
        public string Description { get; set; } = "testing";
        public Head Component;
    }

A "body" Component.

<DetectPrerender MetaInfo="@MetaInfo">
    <Router AppAssembly="typeof(Startup).Assembly" />
</DetectPrerender>


@functions {

    [Parameter]
    private Metatags MetaInfo { get; set; }

}

And a "head" component.

@using example.Shared

<title>@MetaInfo.Title</title>
<meta name="description" content="@MetaInfo.Description">
@{
    MetaInfo.Component = this;
}
@functions {

    [Parameter]
    private Metatags MetaInfo { get; set; }
    private bool ShouldRend = false;
    protected override bool ShouldRender()
    {
        if (ShouldRend)
        {
            ShouldRend = false;
            return true;
        }
        return false;
    }

    public void ShouldRe()
    {
        ShouldRend = true;
        base.Invoke(() => base.StateHasChanged());
    }

}

@TheFanatr
Copy link

That's a nice way to do it where Blazor has full control over most of the document, but it could pose a mild load time issue in certain cases and still requires interop implicitly.

@smartprogrammer93
Copy link

@Honkmother does this work for client side blazor or only server side. And if it does work for client side blazor, can you provide a git repo showing your configuration?

@plasticalligator
Copy link

plasticalligator commented May 9, 2019 via email

@herbertmilhomme
Copy link

@Honkmother I really like the sample code you provided, and i've tried to make attempts at replicating it based on instructions you've provided. I cant tell if preview version i'm using is an issue, or if i'm not following your steps properly. Is it possible for you to offer some more clarity to the sample you've given? Maybe a direct link to download base/template files that i may eventually modify and build on. I'm not sure what your using tag points to on index.cshtml; or if if the code only works for server side or client side... if i'm to use both .razor and .cs files... why i get errors on _generated_component_.Head does not equal inherited (Head)this... if i explicitly reference Head component as an inheritance of ComponentBase, then the other half of code in element builder on html file doesnt work, because ComponentBase.Head is not equal to Head.razor component.

I'm sorry, i just have so much confusion and there's just not enough information and resources available for blazor to properly make sense of anything. Users are writing articles on blazor, but no one is really saying where they get their data from. So their articles don't really touch on any of the concerns i share or look forward to learning more about. I didnt even realize you can only edit the html code for layout only once in index.html till i googled Html.RenderComponentAsync which leads me back to Warnings on <script> in razor components being a thing i found out only recently. I'm dying to create really amazing projects, but i'm so confused that everything looks exactly the same because i cant even modify the layouts dynamically, much less change silly meta-tags for seo and page title. Two weeks of youtube, blogs, articles, github, and 10 visual studio projects later... i find out that you have to edit the wwwroot/index.html to insert your own css/js. //end rant.

I just need a little bit of direction, if you could offer any that'll help clear a bit of the fog (smoke and mirrors)

@mkArtakMSFT mkArtakMSFT transferred this issue from dotnet/blazor Oct 27, 2019
@mkArtakMSFT mkArtakMSFT added the area-blazor Includes: Blazor, Razor Components label Oct 27, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Dec 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests