Back to "为什么你没有在谷歌上找到这个(以及我如何固定它)"

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

为什么你没有在谷歌上找到这个(以及我如何固定它)

Wednesday, 26 November 2025

我经营这个博客已经有一段时间了, 写了有关ASP.NET核心、实体框架、HTMX和各种.NET的技术性文章。然而我的搜索排名却... 很低。在调查了为什么之后, 我发现我用一些新手的错误来破坏我自己的SEO。这里是错的,我是如何修补的。

问题:每页都与谷歌相同

当谷歌爬行你的网站时,它会看到几个关键信号来理解每一页的内容。 我向谷歌发送每页相同的信号。

《吸烟枪:静态元描述》

这是我的 _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页以上的页面, 都有相同的描述, 认为「这个网站有重复的内容问题」或「这个网站不关心提供有用的信息」。

缺失的卡尼基 URL

我有一个多语种博客,

  • /blog/my-article (英文)
  • /blog/my-article/fr (法文)
  • /blog/my-article/de (德文)

谷歌可能认为这些内容是重复内容, 将SEO值通过多个URL去稀释, 而不是将其合并在主页上。

无结构数据

Google的搜索结果可以显示丰富的片段 - 作者信息、 发布日期、 文章预览。 但是, 只有当您告诉 Google 使用此数据时 。 JSON-LLD结构数据我没有

静静社会形象

每页同页 og:image虽然这并不直接影响Google的排名,但如果在社交媒体上分享文章,则会影响点击率,这通过参与信号间接影响SEO。

定法

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语法。

这个结构化数据告诉谷歌:

  • 这是哪类内容 (布洛克插座)
  • 是谁写的 (拥有 URL 以学习更多)
  • 出版和修改时
  • A. 它所涵盖的专题 (类别的关键词)
  • 持续了多久 (数字)
  • 是什么语言 它在

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.";
}

其他SEO基本要求

粒子图

您需要一个站点图。 这里有一个简单的控制器, 动态生成一个 :

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

机器人.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 阅读器。

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

图像呢?

您可能想知道如何为每个文章生成独特的 OG 图像。 对于一个技术博客来说, 这可能不值得努力。 您的流量来自 :

  • 谷歌搜索 - 描述比图像更重要
  • RSS 接口 - 无图像
  • 黑客新闻/编辑 - 缩略图几乎看不见
  • 直接链接 - 开发商共享 URL 开发商共享 URL

一致的品牌形象对识别来说是件好事。 传统形象的生成对于视觉内容、新闻网站或营销页面来说更为重要,因为它们在Facebook和Twitter上竞相点击。

如果您真的想要生成它们, 您可以使用 图像共享 在模板上覆盖文本( 已经用于图像处理) :

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

但对于一个技术博客来说呢?

测试您的 SEO

Google Rich 成果测试

使用使用 Google的丰富结果测试 来验证您的结构化数据。粘贴一个 URL , 它会告诉您您的 JSON- LD 是否有效, 您有资格获得什么丰硕的结果 。

查看页面源

最简单的测试 - 查看页面源并检查 :

  • <meta name="description"> 此页面的独特性吗 ?
  • 是否有 <link rel="canonical">?
  • 是否有 <script type="application/ld+json"> 块吗 ?

Google 搜索控制台

部署后,向 Google 搜索控制台。您也可以使用 URL 检查工具来查看谷歌如何看待您的页面并要求重新索引。

摘要摘要摘要

解决办法直截了当:

问题 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修补 修 修补 问题 修补 修 修补 修 修 修 修 修 修 修 修 修 修补 修 修 修补 修 修 修补 修 修 修 修 修 修 修 修 修 修 修 修 修 修 修 修补 修 修 修 修 修 修 修 修 修 修 修 修 修 修补 修 |---------|-----| 静态元说明 动态 ViewBag.Description 由内容自动生成 { <link rel="canonical"> 到布局

  • 没有结构化数据 * * JSON-LD BlogPosting 每个柱子上都有计谋, 缺少文章元数据 article:published_time, article:author, article:tag 元标记 * 固定页面描述 * * 家庭、博客、联系人、搜索页面的独特描述 *

关键的洞察力: 谷歌无法读懂你的心。如果每页都有相同的描述,谷歌无法知道是什么使每页具有独特性和价值。告诉谷歌每页都是什么,你会得到更好的排名。

让Google几周后重新搜索你的网站,你应该看到改进。SEO是一个漫长的游戏,但这些基本原理是其他一切基础的基础。

继续阅读

logo

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