This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Wednesday, 26 November 2025
Я веду цей блог вже деякий час, пишу детальні технічні статті про ядро ASP.NET, City Framework, HTMX і всі види NET добропорядності.
Коли Google повзає вашим сайтом, він бачить декілька ключових сигналів, щоб зрозуміти, про що йдеться на кожній сторінці.
Ось що моє _Layout.cshtml виглядало так:
<meta name="description" content="Scott Galloway is a lead developer and software engineer with a passion for building web applications.">
<meta property="og:description" content="Scott Galloway is a lead developer and software engineer with a passion for building web applications.">
Все, одинока сторінка, той самий опис, моя стаття про Служби тла у ядрі ASP. NET♪ Архітектура RAG, той самий опис.
Google бачить сторінки 200+ з ідентичними описами і думає, що "цей сайт має дублікати проблем з вмістом" або "цьому сайту байдуже до надання корисної інформації." У будь-якому разі, рейтинги страждають.
У мене є багатомовний блог з перекладами. Той самий вміст існує у:
/blog/my-article (англійською)/blog/my-article/fr (французька)/blog/my-article/de (німецькою)Без канонічних адрес URL, Google може вважати ці адреси дублікатами вмісту, зменшуючи значення SEO у декількох URLх, замість того, щоб об'єднувати їх на головній сторінці.
Результати пошуку Google можуть показувати багато інформації про авторів, опублікувати дати, попередній перегляд статей. Базові дані JSON-LDЯ не была.
Кожна зі сторінок була однаковою og:imageХоча це не безпосередньо впливає на рейтинг Google, це впливає на клік-ударів, коли статті поділяються на соціальні медіа, що непрямо впливає на пошукову оптимізацію через сигнали залучення.
Першою розв' язкою було створення підтримки динамічного опису компонування з розумним поверненням:
@{
var currentUrl = $"https://{Context.Request.Host}{Context.Request.Path}";
var defaultDescription = "Scott Galloway is a lead developer and software engineer with a passion for building web applications.";
var pageDescription = ViewBag.Description as string ?? defaultDescription;
}
<!-- Canonical URL -->
<link rel="canonical" href="@currentUrl" />
<!-- Facebook Meta Tags -->
<meta property="og:url" content="@currentUrl">
<meta property="og:type" content="@(ViewBag.OgType ?? "website")">
<meta property="og:title" content="@ViewBag.Title">
<meta property="og:description" content="@pageDescription">
<meta property="og:site_name" content="mostlylucid" />
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="@ViewBag.Title">
<meta name="twitter:description" content="@pageDescription">
<!-- Meta Description -->
<meta name="description" content="@pageDescription" />
<!-- Article metadata for blog posts -->
@if (ViewBag.PublishedDate != null)
{
<meta property="article:published_time" content="@(((DateTime)ViewBag.PublishedDate).ToString("yyyy-MM-ddTHH:mm:ssZ"))" />
<meta property="article:author" content="Scott Galloway" />
}
@if (ViewBag.Categories != null)
{
foreach (var category in ViewBag.Categories)
{
<meta property="article:tag" content="@category" />
}
}
Тепер компонування читається ViewBag.Description якщо встановлено, повернення до типової адреси. Еквівалентну адресу URL буде автоматично встановлено до поточної адреси URL сторінки.
Кожен допис блогу створює свій опис з перших 155 символів вмісту. Post.cshtml:
@using Mostlylucid.Shared.Helpers
@model Mostlylucid.Models.Blog.BlogPostViewModel
@{
Layout = "_Layout";
ViewBag.Title = $"{Model.Title} ({Model.Language.ConvertCodeToLanguage()})";
// Generate description from plain text content (first 155 chars, truncate at word boundary)
var plainText = Model.PlainTextContent ?? "";
var description = plainText.Length > 155
? plainText.Substring(0, plainText.LastIndexOf(' ', 155)) + "..."
: plainText;
description = description.Replace("\n", " ").Replace("\r", "").Trim();
ViewBag.Description = description;
// Set article metadata
ViewBag.OgType = "article";
ViewBag.PublishedDate = Model.PublishedDate;
ViewBag.Categories = Model.Categories;
// Build canonical URL (without language suffix for English)
var canonicalUrl = Model.Language == "en"
? $"https://{Context.Request.Host}/blog/{Model.Slug}"
: $"https://{Context.Request.Host}/blog/{Model.Slug}/{Model.Language}";
}
Ключові точки тут:
og:type до "шарової" - Повідомляє соціальні платформи Це стаття, а не загальна веб-сторінкаЦе велика частина для багатих фрагментів. Додайте до вашого поля блогу блок скрипту JSON- LD:
<!-- JSON-LD Structured Data for Blog Post -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BlogPosting",
"headline": "@Model.Title",
"description": "@description",
"datePublished": "@Model.PublishedDate.ToString("yyyy-MM-ddTHH:mm:ssZ")",
@if (Model.UpdatedDate.HasValue)
{
@:"dateModified": "@Model.UpdatedDate.Value.ToString("yyyy-MM-ddTHH:mm:ssZ")",
}
"author": {
"@@type": "Person",
"name": "Scott Galloway",
"url": "https://mostlylucid.net/blog/aboutme"
},
"publisher": {
"@@type": "Organization",
"name": "mostlylucid",
"logo": {
"@@type": "ImageObject",
"url": "https://mostlylucid.net/img/logo.svg"
}
},
"mainEntityOfPage": {
"@@type": "WebPage",
"@@id": "@canonicalUrl"
},
"wordCount": @Model.WordCount,
"inLanguage": "@Model.Language",
"keywords": "@string.Join(", ", Model.Categories)",
"image": "https://mostlylucid.net/img/social2.jpg"
}
</script>
Зауважте @@ тікайте, щоб уникнути @ символ у переглядах Razor - використання JSON- LD @context і @type який в іншому випадку інтерпретується як синтаксис Razor.
Ці структуровані дані говорять Google:
Google може використовувати цю можливість, щоб показати багато фрагментів у результатах пошуку, зокрема, дані авторів, дати оприлюднення тощо.
Не забудьте ваші статичні сторінки. Кожен з них повинен мати унікальний, доречний опис:
<!-- Home page -->
@{
ViewBag.Title = "mostlylucid- Scott Galloway's Developer Blog";
ViewBag.Description = "Technical blog covering ASP.NET Core, C#, Entity Framework, HTMX, Docker, and modern web development. Practical tutorials, NuGet packages, and open source projects.";
}
<!-- Blog index -->
@{
ViewBag.Title = "Blog Posts";
ViewBag.Description = "Technical articles on ASP.NET Core, C#, Entity Framework, Docker, and modern web development. Practical tutorials and real-world examples from a lead developer.";
}
<!-- Contact page -->
@{
ViewBag.Title = "Contact Scott Galloway";
ViewBag.Description = "Get in touch with Scott Galloway. Questions about ASP.NET Core, C#, web development, or collaboration opportunities? Send me a message.";
}
<!-- Search page -->
@{
ViewBag.Title = "Search Results";
ViewBag.Description = "Search through technical articles on ASP.NET Core, C#, Entity Framework, and web development. Find tutorials, guides, and solutions.";
}
Вам потрібна карта сайтів. Ось простий контролер, який створює її динамічно:
public class SiteMapController(
IBlogViewService blogViewService,
IHttpContextAccessor httpContextAccessor) : Controller
{
[HttpGet]
[ResponseCache(Duration = 43200)] // Cache for 12 hours
public async Task<IActionResult> Index()
{
var pages = await blogViewService.GetPosts();
var siteUrl = $"https://{httpContextAccessor.HttpContext?.Request.Host}";
XNamespace sitemap = "http://www.sitemaps.org/schemas/sitemap/0.9";
var feed = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement(sitemap + "urlset",
from page in pages
select new XElement(sitemap + "url",
new XElement(sitemap + "loc", $"{siteUrl}/blog/{page.Slug}"),
new XElement(sitemap + "lastmod", page.PublishedDate.ToString("yyyy-MM-dd")),
new XElement(sitemap + "changefreq", "weekly"),
new XElement(sitemap + "priority", "0.8")
)
)
);
return Content(feed.ToString(), "text/xml");
}
}
Зареєструвати маршрут:
app.MapControllerRoute(
name: "sitemap",
pattern: "sitemap.xml",
defaults: new { controller = "SiteMap", action = "Index" });
Повідомити пошуковим рушіям, де знаходиться карта вашого сайту, і про те, що слід пересуватися:
app.MapGet("/robots.txt", async context =>
{
var siteUrl = $"https://{context.Request.Host}";
var robotsTxt = $"""
User-agent: *
Allow: /
Sitemap: {siteUrl}/sitemap.xml
""";
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(robotsTxt);
});
Багато розробників користуються читачами RSS. Використання подачі RSS також допомагає з можливістю відкриття:
<link rel="alternate" type="application/atom+xml"
title="RSS Feed for mostlylucid.net"
href="https://mostlylucid.net/rss" />
Вам, можливо, цікаво створити унікальні зображення OG для кожного допису. Для технічного блогу це, ймовірно, не варте зусиль. Ваш трафік походить від:
Постійне брендове зображення підходить для розпізнавання. Створення нетипових зображень має більше значення для візуального вмісту, сайтів новин або маркетингових сторінок, які змагаються за натискання на Facebook і Twitter.
Якщо ви справді бажаєте їх створити, ви можете скористатися Розширення зображеньComment (яку я вже використовував для обробки зображень) для накладання тексту на шаблон:
public async Task<string> GenerateOgImage(string title, string slug)
{
using var image = await Image.LoadAsync("wwwroot/img/og-template.png");
var font = SystemFonts.CreateFont("Arial", 48, FontStyle.Bold);
image.Mutate(x => x.DrawText(
new RichTextOptions(font)
{
Origin = new PointF(50, 200),
WrappingLength = 1100
},
title,
Color.White));
var outputPath = $"wwwroot/og/{slug}.png";
await image.SaveAsPngAsync(outputPath);
return $"/og/{slug}.png";
}
Але для технічного блогу?
Користування Тест багатих результатів Google для перевірки структури ваших даних. Вставте адресу URL і вона повідомить вам, чи ваш JSON- LD є коректним і для чого потрібні багаті результати.
Найпростіша перевірка - перегляд джерел сторінок і перевірка:
<meta name="description"> унікально для цієї сторінки?<link rel="canonical">?<script type="application/ld+json"> блок?Після запуску надішліть вашу карту сайта до Консоль пошуку GoogleВи також можете скористатися інструментом Inspection, щоб побачити, як Google бачить ваші сторінки та запити на повторне дослідження.
Виправлення були простими:
♪
|---------|-----|
ViewBag.Description з автопоколінням з вмісту}
Без адрес URL * * * BAR\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ >\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ > <link rel="canonical"> до circle сягменту
♪ Без об'єму даних} JSON- LD BlogPosting schema on every post}
article:published_time, article:author, article:tag Метастази
ТАЙМЕТИЧНА ПАСТКА: Home, Blog, Conact, Search pages ♪Головне розуміння: Гугл не може читати ваші думки... Якщо кожна сторінка має однаковий опис, Гугл не може знати, що робить кожну сторінку унікальною і цінною.
Дайте Google декілька тижнів для перерозподілу вашого сайту, і ви побачите покращення. SEO - це довга гра, але ці фундаментальні принципи є основою всього іншого.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.