Skip to content

feat(seo): add per-page descriptions and canonical metadata - #1531

Merged
BenjaminMichaelis merged 3 commits into
mainfrom
benjaminmichaelis/per-page-meta-descriptions
Oct 17, 2026
Merged

BenjaminMichaelis merged 3 commits into
mainfrom
benjaminmichaelis/per-page-meta-descriptions

Conversation

@BenjaminMichaelis

@BenjaminMichaelis BenjaminMichaelis commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Why

Every page currently receives the same site-wide description, and og:url always points at the homepage. This makes chapter and static-page metadata misleading to search engines and link previews. It also has to be correct on both essentialcsharp.com and dev.essentialcsharp.com.

What changed

  • Give public MVC actions page-specific descriptions. Chapter pages read ParserCli-generated chapter descriptions when present, with a heading-based fallback when the generated file is absent.
  • Emit exactly one canonical <link> and a matching og:url on every successful, non-Identity page, excluding query strings. The origin always comes from SiteSettings:BaseUrl.
    • Chapter pages keep the path from the parser-generated canonical (so aliases such as /hello-world point to /introducing-c), re-based from the parser's hardcoded production origin onto the configured origin.
    • The parser's own canonical tag is stripped from the injected head.
  • Share the selected description across HTML, Open Graph, and Twitter metadata, while retaining the layout fallback for pages without an override.
  • Cover canonical and description output for both production and dev origins with functional tests; document the metadata contract.

Supersedes #1130

This reconciles #1130 and addresses its review findings:

  • Duplicate/conflicting canonical on aliases: fixed. There is one canonical, and it points to the primary page.
  • X-Forwarded-Host without an allowlist: avoided. The request Host is not used for canonicals, so XForwardedHost is not enabled. That flag would also have let a trusted proxy poison emailed reset/confirmation links.
  • XForwardedHost is a no-op under ASPNETCORE_FORWARDEDHEADERS_ENABLED=true: moot for the same reason.

Also addresses this PR's own review comment: metadata tests now pin SiteSettings:BaseUrl instead of depending on appsettings.Development.json.

Deployment note

Each environment must set SiteSettings__BaseUrl to its own origin, for example https://dev.essentialcsharp.com for dev. Production already depends on this for the www→apex redirect.

Validation

The full Web test suite passes locally (186 tests), built with /p:SkipFrontendBuild=true. The local frontend build is blocked by an unrelated stale @zxcvbn-ts/core install.

@BenjaminMichaelis
BenjaminMichaelis marked this pull request as ready for review October 17, 2026 01:02
@BenjaminMichaelis
BenjaminMichaelis requested a balanced review from Copilot October 17, 2026 01:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Static-page metadata tests expect production URLs but the default test configuration supplies a localhost origin.

1 open issue
What changed in this PR

Adds page-specific SEO metadata while preserving parser-generated chapter canonicals.

Changes:

  • Adds public-page descriptions and optional generated chapter descriptions with heading fallbacks.
  • Shares descriptions across metadata formats and aligns Open Graph URLs with canonicals.
  • Adds metadata tests and documents the contract.
File Description
README.md Documents metadata selection and fallbacks.
EssentialCSharp.Web/​Views/​Shared/​_Layout.cshtml Renders descriptions and canonical metadata.
EssentialCSharp.Web/​Services/​ChapterDescriptionService.cs Loads generated descriptions with heading fallbacks.
EssentialCSharp.Web/​Program.cs Registers the description service.
EssentialCSharp.Web/​Controllers/​McpSetupController.cs Adds the setup-page description.
EssentialCSharp.Web/​Controllers/​HomeController.cs Sets descriptions and preserves chapter canonicals.
EssentialCSharp.Web.Tests/​FunctionalTests.cs Checks rendered descriptions and canonicals.
EssentialCSharp.Web.Tests/​ChapterDescriptionServiceTests.cs Tests generated descriptions and fallbacks.

Review effort: Balanced

Comment thread EssentialCSharp.Web.Tests/FunctionalTests.cs
Re-base parser chapter canonicals onto SiteSettings:BaseUrl, keeping the primary page path for alias routes, and strip the parser canonical so the layout renders exactly one canonical/og:url. Pin BaseUrl in metadata tests and cover dev.essentialcsharp.com. Supersedes the request-Host approach from #1130 without enabling X-Forwarded-Host.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Static review identified no unresolved blocking issues; tests were not executed during this review.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@BenjaminMichaelis
BenjaminMichaelis merged commit ae4fc9e into main Oct 17, 2026
29 checks passed
@BenjaminMichaelis
BenjaminMichaelis deleted the benjaminmichaelis/per-page-meta-descriptions branch October 17, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants