Back to "Чому ви не знайшли це в Google (і як я його виписував)"

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

ASP.NET Core SEO

Чому ви не знайшли це в Google (і як я його виписував)

Wednesday, 26 November 2025

Я веду цей блог вже деякий час, пишу детальні технічні статті про ядро ASP.NET, City Framework, HTMX і всі види NET добропорядності.

Проблема: кожна сторінка виглядала однаковою до Google

Коли 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+ з ідентичними описами і думає, що "цей сайт має дублікати проблем з вмістом" або "цьому сайту байдуже до надання корисної інформації." У будь-якому разі, рейтинги страждають.

Відсутні Canonical URL

У мене є багатомовний блог з перекладами. Той самий вміст існує у:

  • /blog/my-article (англійською)
  • /blog/my-article/fr (французька)
  • /blog/my-article/de (німецькою)

Без канонічних адрес URL, Google може вважати ці адреси дублікатами вмісту, зменшуючи значення SEO у декількох URLх, замість того, щоб об'єднувати їх на головній сторінці.

Немає структурованих даних

Результати пошуку Google можуть показувати багато інформації про авторів, опублікувати дати, попередній перегляд статей. Базові дані JSON-LDЯ не была.

Статичні соціальні зображення

Кожна зі сторінок була однаковою og:imageХоча це не безпосередньо впливає на рейтинг Google, це впливає на клік-ударів, коли статті поділяються на соціальні медіа, що непрямо впливає на пошукову оптимізацію через сигнали залучення.

Фіксації

1. Динамічні мета описи

Першою розв' язкою було створення підтримки динамічного опису компонування з розумним поверненням:

@{
    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 сторінки.

2. Автоматичні описи дописів блогу

Кожен допис блогу створює свій опис з перших 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}";
}

Ключові точки тут:

  1. Обрізати на межі слова - Ми знаходимо останній проміжок до 155 символів, щоб уникнути скорочення слів навпіл.
  2. Скидати рядки - Мета описи повинні бути однорядковими
  3. Встановити og:type до "шарової" - Повідомляє соціальні платформи Це стаття, а не загальна веб-сторінка
  4. Передати метадані статті - Оприлюднена дата і категорії передаються до компонування

3. Дані структури JSON- LD

Це велика частина для багатих фрагментів. Додайте до вашого поля блогу блок скрипту 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:

  • Що це за тип вмісту (Блог-Позиція)
  • Хто це написав? (Об' єднується з URL, щоб дізнатися більше)
  • Після опублікування і зміни
  • Що пов'язано з темою (ключі з категорій)
  • Скільки часу минуло (Слово число)
  • Якою мовою вона є в

Google може використовувати цю можливість, щоб показати багато фрагментів у результатах пошуку, зокрема, дані авторів, дати оприлюднення тощо.

4. Унікальні описи сторінок ключів

Не забудьте ваші статичні сторінки. Кожен з них повинен мати унікальний, доречний опис:

<!-- 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" });

robots. txt

Повідомити пошуковим рушіям, де знаходиться карта вашого сайту, і про те, що слід пересуватися:

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. Використання подачі RSS також допомагає з можливістю відкриття:

<link rel="alternate" type="application/atom+xml"
      title="RSS Feed for mostlylucid.net"
      href="https://mostlylucid.net/rss" />

А що сказати про зображення?

Вам, можливо, цікаво створити унікальні зображення OG для кожного допису. Для технічного блогу це, ймовірно, не варте зусиль. Ваш трафік походить від:

  • Пошук у Google - описи мають більше значення, ніж зображення
  • Подачі RSS - немає зображень
  • Hacker News / Reddit - Мініатюри ледь помітні
  • Прямі посилання - розробники з спільними адресами URL

Постійне брендове зображення підходить для розпізнавання. Створення нетипових зображень має більше значення для візуального вмісту, сайтів новин або маркетингових сторінок, які змагаються за натискання на 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

Користування Тест багатих результатів Google для перевірки структури ваших даних. Вставте адресу URL і вона повідомить вам, чи ваш JSON- LD є коректним і для чого потрібні багаті результати.

Переглянути джерело сторінки

Найпростіша перевірка - перегляд джерел сторінок і перевірка:

  • Чи є <meta name="description"> унікально для цієї сторінки?
  • Є є <link rel="canonical">?
  • Є є <script type="application/ld+json"> блок?

Консоль пошуку Google

Після запуску надішліть вашу карту сайта до Консоль пошуку 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 - це довга гра, але ці фундаментальні принципи є основою всього іншого.

Подальше читання

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.