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?
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.
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 :-)
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.
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.
@SteveSandersonMS How do you change the content of the
@DNF-SaS Yes
@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.
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 !
@Andrew-MSFT SAP always face the issue meta tags and title with the SEO :( haiz.. so.. Blazor has nothing diff about that, right?
@topnguyen Correct, the story is pretty much the same.
I have similar problem for seo purposes,
I need to be able to modifiy title and description tags for every "page"
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";
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 https://github.com/aspnet/AspNetCore/issues/9834 )
Regards!
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.
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());
}
}
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.
@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?
@TheFanatr
Yes it requires some minimal interop between the components; but doesn't need any Javascript.Â
I think this is close to as good as we can get in terms of patterns that already exists within Blazor. I wouldn't mind seeing default HTML, Body, and Head component classes with Dictionary structures, and having those references passed down a cascade or something.
re: load time: not as far as I can tell.
@smartprogrammer93
It should work for both, but I would have to double check because for all I know
Client side might be a little different because you need the script tag in the document for Blazor to even boot up (where as serverside renders the components for you). I am 99% certain you can just throw the script tag inside of the loading placeholder.
After I'm done working today I will set up a repo with an example that works with preview4/5 for both the client and server.
@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
Most helpful comment
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
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.
A "body" Component.
And a "head" component.