diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/LayoutService.cs b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/LayoutService.cs
index 9bf823e..371c883 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/LayoutService.cs
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/LayoutService.cs
@@ -1,21 +1,22 @@
namespace dymaptic.GeoBlazor.Core.Sample.Shared.Shared;
-// LayoutStateService.cs
public class LayoutService
{
public SamplePage? CurrentPage { get; private set; }
+
+ // URI the CurrentPage was registered for. Consumers should only treat
+ // CurrentPage as live when this matches the layout's current URI —
+ // that way stale references from a previously-rendered SamplePage are
+ // silently ignored after navigation, with no race against component
+ // initialization order.
+ public string? PageUri { get; private set; }
+
public event Action? OnPageChanged;
- public void SetCurrentPage(SamplePage page)
+ public void SetCurrentPage(SamplePage page, string uri)
{
CurrentPage = page;
- OnPageChanged?.Invoke();
- }
-
- public void ClearCurrentPage()
- {
- if (CurrentPage is null) return;
- CurrentPage = null;
+ PageUri = uri;
OnPageChanged?.Invoke();
}
}
\ No newline at end of file
diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor
index 5fe5762..c6558b3 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor
@@ -23,13 +23,13 @@
-
+
- @if (LayoutService.CurrentPage is not null)
+ @if (EffectiveCurrentPage is { } page)
{
- for (int i = 0; i < LayoutService.CurrentPage.PageLinks.Count; i++)
+ for (int i = 0; i < page.PageLinks.Count; i++)
{
- NavMenu.PageLink pageLink = LayoutService.CurrentPage.PageLinks[i];
+ NavMenu.PageLink pageLink = page.PageLinks[i];
string btnType = _btnTypes[i % _btnTypes.Length];
@pageLink.Title
}
@@ -38,7 +38,7 @@
@Body
- @if (LayoutService.CurrentPage is { Description: { Length: > 0 } description })
+ @if (EffectiveCurrentPage is { Description: { Length: > 0 } description })
{
About this sample
@@ -78,7 +78,7 @@
public required RouteData RouteData { get; set; }
protected override void OnInitialized()
- {
+ {
base.OnInitialized();
LayoutService.OnPageChanged += StateHasChanged;
@@ -87,11 +87,13 @@
private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
- // Clear before the new page renders. If the new page inherits SamplePage,
- // its OnInitialized will set CurrentPage again. Pages that don't inherit
- // SamplePage (Home, NotFound, etc.) leave it null so the layout doesn't
- // render stale per-page metadata.
- LayoutService.ClearCurrentPage();
+ // URI-dependent metadata (title, og:url, JSON-LD @id) needs to update on
+ // every navigation, even before the next page's OnInitialized runs. We
+ // deliberately don't clear LayoutService.CurrentPage here — the
+ // EffectiveCurrentPage getter discards stale references whose URI no
+ // longer matches, which avoids a render-order race between the layout
+ // and the new SamplePage's initialization.
+ StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -127,6 +129,19 @@
private string CurrentSlug => NavigationManager.ToBaseRelativePath(CanonicalPageUrl);
+ // Only treat LayoutService.CurrentPage as live when its registered URI
+ // matches the URI the layout is currently rendering for. This guards
+ // against two cases:
+ // - Stale CurrentPage from a previous SamplePage after navigating to a
+ // non-SamplePage route (Home, NotFound, etc).
+ // - Render-order races where the new SamplePage hasn't yet run its
+ // OnInitialized but the layout has already started a render pass.
+ private SamplePage? EffectiveCurrentPage =>
+ LayoutService.CurrentPage is { } page &&
+ string.Equals(LayoutService.PageUri, NavigationManager.Uri, StringComparison.Ordinal)
+ ? page
+ : null;
+
private string MetaTitle
{
get
@@ -134,7 +149,7 @@
string slug = CurrentSlug;
if (string.IsNullOrEmpty(slug)) return "GeoBlazor Samples";
string title = SlugToTitle(slug);
- return LayoutService.CurrentPage is not null
+ return EffectiveCurrentPage is not null
? $"{title} — GeoBlazor Sample"
: title;
}
@@ -144,7 +159,7 @@
{
get
{
- string? d = LayoutService.CurrentPage?.Description;
+ string? d = EffectiveCurrentPage?.Description;
if (string.IsNullOrWhiteSpace(d))
{
return DefaultMetaDescription;
@@ -166,7 +181,34 @@
string pageUrl = CanonicalPageUrl;
string siteUrl = NavigationManager.BaseUri.TrimEnd('/');
string slug = CurrentSlug;
- string pageName = string.IsNullOrEmpty(slug) ? "GeoBlazor Samples" : SlugToTitle(slug);
+ bool isHome = string.IsNullOrEmpty(slug);
+ string pageName = isHome ? "GeoBlazor Samples" : SlugToTitle(slug);
+ SamplePage? currentPage = EffectiveCurrentPage;
+
+ Dictionary webPage = new()
+ {
+ ["@type"] = "WebPage",
+ ["@id"] = $"{pageUrl}#webpage",
+ ["url"] = pageUrl,
+ ["name"] = pageName,
+ ["isPartOf"] = new Dictionary { ["@id"] = $"{siteUrl}/#website" },
+ ["about"] = new Dictionary { ["@id"] = "https://geoblazor.com/#software" }
+ };
+
+ string? pageDescription = currentPage?.Description;
+ if (!string.IsNullOrWhiteSpace(pageDescription))
+ {
+ webPage["description"] = pageDescription;
+ }
+
+ // Per-page keywords: the page title's words combined with the site
+ // baseline. Gives each WebPage entry distinguishable content beyond
+ // url/name so crawlers see meaningful variation per sample.
+ if (currentPage is not null && !isHome)
+ {
+ webPage["keywords"] = BuildKeywords(pageName);
+ webPage["mainEntity"] = new Dictionary { ["@id"] = $"{pageUrl}#code" };
+ }
List