fix: HTTPS URLs in sitemap, robots, canonical + og:image on homepage
CI/CD / CI · dotnet build (push) Failing after 5m22s
CI/CD / Deploy · drsousan (push) Has been skipped

- Add UseForwardedHeaders middleware so Request.Scheme = "https" behind nginx
- Add SITE_BASE_URL env var fallback for sitemap.xml, robots.txt, and all
  Razor page canonical/og URLs — set it to https://draletaha.ir in .env
- Add og:image to homepage using hero photo
- Add SITE_BASE_URL to docker-compose.yml environment block

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 22:30:55 +03:30
parent 22d0ecb330
commit d02a5963cf
5 changed files with 27 additions and 6 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
@section Head { @section Head {
<title>@ViewData["Title"]</title> <title>@ViewData["Title"]</title>
<meta name="description" content="مقالات تخصصی دکتر سوسن آل‌طه درباره زیبایی پوست، بوتاکس، فیلر، لیزر و مراقبت از پوست." /> <meta name="description" content="مقالات تخصصی دکتر سوسن آل‌طه درباره زیبایی پوست، بوتاکس، فیلر، لیزر و مراقبت از پوست." />
<link rel="canonical" href="@(Request.Scheme + "://" + Request.Host + "/blog")" /> <link rel="canonical" href="@((Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (Request.Scheme + "://" + Request.Host)) + "/blog")" />
<style> <style>
/* ─── Blog Hero ─────────────────────────────────────────────── */ /* ─── Blog Hero ─────────────────────────────────────────────── */
.blog-hero{background:linear-gradient(135deg,var(--gold-pale) 0%,#EDE0CA 100%);padding:6rem 2rem 3rem;text-align:center} .blog-hero{background:linear-gradient(135deg,var(--gold-pale) 0%,#EDE0CA 100%);padding:6rem 2rem 3rem;text-align:center}
+1 -1
View File
@@ -2,7 +2,7 @@
@model DrSousan.Api.Pages.Blog.PostModel @model DrSousan.Api.Pages.Blog.PostModel
@{ @{
var post = Model.Post!; var post = Model.Post!;
var baseUrl = Request.Scheme + "://" + Request.Host; var baseUrl = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (Request.Scheme + "://" + Request.Host);
var canonicalUrl = baseUrl + "/blog/" + post.Slug; var canonicalUrl = baseUrl + "/blog/" + post.Slug;
var ogImage = ViewData["OgImage"]?.ToString() ?? ""; var ogImage = ViewData["OgImage"]?.ToString() ?? "";
var articleType = ViewData["ArticleType"]?.ToString() ?? "MedicalWebPage"; var articleType = ViewData["ArticleType"]?.ToString() ?? "MedicalWebPage";
+10 -2
View File
@@ -1,6 +1,7 @@
@page @page
@model IndexModel @model IndexModel
@{ @{
var siteBaseUrl = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (siteBaseUrl);
var h = Model.Hero; var h = Model.Hero;
var a = Model.About; var a = Model.About;
var c = Model.Contact; var c = Model.Contact;
@@ -24,14 +25,21 @@
<meta property="og:title" content="@ViewData["Title"]" /> <meta property="og:title" content="@ViewData["Title"]" />
<meta property="og:description" content="@h.GetValueOrDefault("subtitle","")" /> <meta property="og:description" content="@h.GetValueOrDefault("subtitle","")" />
<meta property="og:locale" content="fa_IR" /> <meta property="og:locale" content="fa_IR" />
<link rel="canonical" href="@(Request.Scheme + "://" + Request.Host + "/")" /> @if (!string.IsNullOrEmpty(heroImg))
{
var absHeroImg = heroImg.StartsWith("http") ? heroImg : (siteBaseUrl + heroImg);
<meta property="og:image" content="@absHeroImg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
}
<link rel="canonical" href="@(siteBaseUrl + "/")" />
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@@context":"https://schema.org", "@@context":"https://schema.org",
"@@type":["MedicalBusiness","LocalBusiness"], "@@type":["MedicalBusiness","LocalBusiness"],
"name":"@siteName", "name":"@siteName",
"description":"@h.GetValueOrDefault("subtitle","")", "description":"@h.GetValueOrDefault("subtitle","")",
"url":"@(Request.Scheme + "://" + Request.Host)", "url":"@(siteBaseUrl)",
"telephone":"@c.GetValueOrDefault("phone","")", "telephone":"@c.GetValueOrDefault("phone","")",
"address":{"@@type":"PostalAddress","addressLocality":"تهران","addressCountry":"IR","streetAddress":"@c.GetValueOrDefault("address","")"}, "address":{"@@type":"PostalAddress","addressLocality":"تهران","addressCountry":"IR","streetAddress":"@c.GetValueOrDefault("address","")"},
"openingHours":"@c.GetValueOrDefault("hours","")", "openingHours":"@c.GetValueOrDefault("hours","")",
+14 -2
View File
@@ -56,6 +56,15 @@ builder.Services.ConfigureHttpJsonOptions(opts =>
// ── Build ───────────────────────────────────────────────────────────────────── // ── Build ─────────────────────────────────────────────────────────────────────
var app = builder.Build(); var app = builder.Build();
// Trust the X-Forwarded-Proto header from nginx so ctx.Request.Scheme = "https"
// This fixes canonical URLs, sitemap, robots.txt, og:image all using http:// on production
app.UseForwardedHeaders(new Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor
| Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
});
app.UseCors(); app.UseCors();
app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages) app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages)
app.UseStaticFiles(); app.UseStaticFiles();
@@ -780,7 +789,9 @@ app.MapDelete("/api/health-requests/{id:int}", async (int id, AppDbContext db) =
// ── Sitemap ─────────────────────────────────────────────────────────────────── // ── Sitemap ───────────────────────────────────────────────────────────────────
app.MapGet("/sitemap.xml", async (AppDbContext db, HttpContext ctx) => app.MapGet("/sitemap.xml", async (AppDbContext db, HttpContext ctx) =>
{ {
var baseUrl = $"{ctx.Request.Scheme}://{ctx.Request.Host}"; // SITE_BASE_URL env var wins (e.g. "https://draletaha.ir") — falls back to request scheme+host
var baseUrl = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/')
?? $"{ctx.Request.Scheme}://{ctx.Request.Host}";
var published = await db.BlogPosts.Where(p => p.IsPublished) var published = await db.BlogPosts.Where(p => p.IsPublished)
.Select(p => new { p.Slug, p.UpdatedAt }).ToListAsync(); .Select(p => new { p.Slug, p.UpdatedAt }).ToListAsync();
@@ -814,7 +825,8 @@ app.MapGet("/healthz", () => Results.Ok(new { status = "healthy", utc = DateTime
// ── Robots.txt ──────────────────────────────────────────────────────────────── // ── Robots.txt ────────────────────────────────────────────────────────────────
app.MapGet("/robots.txt", (HttpContext ctx) => app.MapGet("/robots.txt", (HttpContext ctx) =>
{ {
var host = $"{ctx.Request.Scheme}://{ctx.Request.Host}"; var host = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/')
?? $"{ctx.Request.Scheme}://{ctx.Request.Host}";
var body = $"User-agent: *\nAllow: /\nDisallow: /admin/\nDisallow: /api/\n\nSitemap: {host}/sitemap.xml"; var body = $"User-agent: *\nAllow: /\nDisallow: /admin/\nDisallow: /api/\n\nSitemap: {host}/sitemap.xml";
ctx.Response.ContentType = "text/plain"; ctx.Response.ContentType = "text/plain";
return ctx.Response.WriteAsync(body); return ctx.Response.WriteAsync(body);
+1
View File
@@ -23,6 +23,7 @@ services:
Admin__Username: "${ADMIN_USERNAME:-admin}" Admin__Username: "${ADMIN_USERNAME:-admin}"
Admin__Password: "${ADMIN_PASSWORD:-admin123}" Admin__Password: "${ADMIN_PASSWORD:-admin123}"
ASPNETCORE_ENVIRONMENT: "Production" ASPNETCORE_ENVIRONMENT: "Production"
SITE_BASE_URL: "${SITE_BASE_URL:-}"
volumes: volumes:
db_data: db_data: