Back to "Miksi et löytänyt tätä Googlesta (ja miten korjasin sen)"

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

Miksi et löytänyt tätä Googlesta (ja miten korjasin sen)

Wednesday, 26 November 2025

Olen pyörittänyt tätä blogia jo jonkin aikaa, kirjoittanut yksityiskohtaisia teknisiä artikkeleita ASP.NET Coresta, Entity Frameworkista, HTMX:stä ja kaikenlaisista .NET-hyvistä. Kuitenkin hakurankingini olivat... altavastaavia. Tutkittuani viimein miksi, huomasin sabotoineeni omaa SEO:tani joillakin aloittelijavirheillä.

Ongelma: Jokainen sivu näytti Googlelle samanlaiselta

Kun Google ryömii sivustoasi, se katsoo useita keskeisiä signaaleja ymmärtääkseen, mistä kussakin sivussa on kyse. Lähetin Googlelle samoja signaaleja jokaiselle sivulle.

Tupakointiase: Staattinen metakuvaus

Tässä on mitä minun _Layout.cshtml näytti:

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

Jokainen. Single. Page. Sama kuvaus. Artikkelini Taustapalvelut ASP.NET CoressaSama kuvaus kuin kotisivullani. RAG-arkkitehtuuriSama kuvaus.

Google näkee 200+-sivua, joilla on samanlaiset kuvaukset, ja katsoo, että "tällä sivustolla on päällekkäisiä sisältöongelmia" tai "tämä sivusto ei välitä hyödyllisestä tiedosta".

Puuttuvat kanoniset URL-osoitteet

Minulla on monikielinen blogi, jossa on käännöksiä. Samaa sisältöä löytyy osoitteesta:

  • /blog/my-article (englanti)
  • /blog/my-article/fr (ranska)
  • /blog/my-article/de (saksalainen)

Ilman kanonisia URL-osoitteita Google saattaa nähdä nämä sisällön kaksoiskappaleina, jotka laimentavat SEO-arvoa useiden URL-osoitteiden välillä sen sijaan, että ne vahvistettaisiin pääsivulle.

Ei jäsenneltyjä tietoja

Googlen hakutuloksissa voi näkyä runsaasti näppäimiä - tekijän tietoja, julkaisupäiviä, artikkelien esikatseluja. Mutta vain, jos kerrot Googlelle näistä tiedoista käyttämällä JSON-LD:n jäsennelty dataEn ollut.

Staattinen sosiaalinen kuva

Jokainen sivu jakoi saman og:image. Vaikka tämä ei suoraan vaikuta Googlen rankingiin, se vaikuttaa klikkauksen läpi -hintoihin, kun artikkeleita jaetaan sosiaalisessa mediassa - mikä vaikuttaa epäsuorasti SEO:han sitoutumissignaalien kautta.

Korjaukset

1. Dynamic Meta Descriptions

Ensimmäinen korjaus oli layout-tuen dynaamisten kuvausten tekeminen järkevällä varalla:

@{
    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" />
    }
}

Nyt asettelussa lukee: ViewBag.Description Jos valittu, takaisin oletukseen. Kanoninen URL asetetaan automaattisesti nykyiselle sivulle URL.

2. Autogeneroidut kuvaukset blogikirjoituksiin

Jokainen blogikirjoitus tuottaa nyt kuvaustaan sisällön 155 ensimmäisestä merkistä. 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}";
}

Tärkeimmät seikat ovat seuraavat:

  1. Runkaa sanarajalla - Viimeisen tilan ennen 155 merkkiä vältämme leikkaamasta sanoja kahtia
  2. Poista uudet linjat - Metakuvausten pitäisi olla yksirivisiä
  3. Aseta og:type "hiukkanen" - Kertoo sosiaalisille alustoille, että kyseessä on artikkeli, ei yleinen verkkosivu
  4. Läpäise artikkelin metatiedot - Julkaistu päivämäärä ja kategoriat kulkevat layoutiin

3. JSON-LD Structured Data

Lisää JSON-LD-skriptiblokki blogisi postausnäkymään:

<!-- 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>

Huomaa, että @@ Pakeneminen @ symboli Razorin näköpiirissä - JSON-LD käyttää @context sekä @type joka muuten tulkittaisiin Razor syntaksiksi.

Tämä jäsennelty data kertoo Googlelle:

  • Millaista sisältöä tämä on? (BlogPostia)
  • Kuka sen kirjoitti? (Henkilö, jolla on URL ja lisätietoja)
  • Kun se julkaistiin ja muokattiin
  • Mitä aiheita siinä käsitellään? (avainsanat kategorioista)
  • Kuinka kauan se kestää? (sanamäärä)
  • Millä kielellä se on?

Google voi käyttää tätä näyttääkseen runsaasti hakutuloksia, mukaan lukien tekijän tiedot, julkaistakseen päivämääriä ja paljon muuta.

4. Ainutlaatuiset kuvaukset avainsivuille

Älä unohda staattisia sivujasi. Jokaisessa pitäisi olla ainutlaatuinen, merkityksellinen kuvaus:

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

Muut SEO-vaatimukset

Sivukartta

Tarvitset sivustokartan. Tässä yksinkertainen ohjain, joka tuottaa yhden dynaamisesti:

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

Rekisteröi reitti:

app.MapControllerRoute(
    name: "sitemap",
    pattern: "sitemap.xml",
    defaults: new { controller = "SiteMap", action = "Index" });

robotit.txt

Kerro hakukoneille, missä sivustokarttasi on ja mitä ryömiä:

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-syöte

Monet kehittäjät käyttävät RSS-lukijoita. RSS-syötteen käyttö auttaa myös löytämisessä:

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

Entä kuvat?

Saatat miettiä ainutlaatuisten OG-kuvien tuottamista jokaiseen postaukseen. Teknisessä blogissa se ei todennäköisesti ole vaivan arvoista. Liikenne tulee:

  • Google-haku - Kuvauksilla on enemmän merkitystä kuin kuvilla
  • RSS-syötteet - ei kuvia
  • Hacker News / Reddit - kynnet hädin tuskin näkyvissä
  • Suorat linkit - URL-osoitteita jakavat kehittäjät

Johdonmukainen brändätty kuva sopii hyvin tunnistettavaksi. Custom-kuvasukupolvella on enemmän merkitystä visuaaliselle sisällölle, uutissivustoille tai markkinointisivuille, jotka kilpailevat Facebookin ja Twitterin klikkauksista.

Jos haluat luoda ne, voit käyttää ImageSharp (jota käytän jo kuvankäsittelyyn) tekstin päällystämiseen mallilla:

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

Mutta teknisessä blogissa saat mitä sinulla on.

SEO:n testaaminen

Google Richin tulostesti

Käyttö Googlen runsaat tulokset -testi Vahvistaaksesi jäsennellyt tietosi. Liitä URL ja se kertoo, onko JSON-LD pätevä ja mitä hyviä tuloksia olet oikeutettu saamaan.

Näytä sivulähde

Yksinkertaisin testi - katso sivun lähde ja tarkista:

  • Onko <meta name="description"> ainutlaatuista tälle sivulle?
  • Onko olemassa <link rel="canonical">?
  • Onko olemassa <script type="application/ld+json"> Block?

Google-hakukonsoli

Käyttöönoton jälkeen lähetä sivustokarttasi Google-hakukonsoli. Voit myös käyttää URL Inspection -työkalua nähdäksesi tarkasti, miten Google näkee sivusi ja pyytää uudelleen indeksointia.

Yhteenveto

Korjaukset olivat yksinkertaisia:

Ongelma korjaantuu |---------|-----| Staattinen metakuvaus Dynamic ViewBag.Description Autogeneration from content Ei kanonisia URL-osoitteita Lisätty <link rel="canonical"> asetteluun Ei jäsenneltyjä tietoja JSON-LD BlogPosting Skema jokaisessa virassa Puuttuvat artikkelin metatiedot article:published_time, article:author, article:tag Metatunnisteet Staattinen sivun kuvaukset Ainutlaatuinen kuvaukset Kotiin, Blogi, Yhteystiedot, Hakusivut

Avainnäkemys: Google ei osaa lukea ajatuksiasi. Jos jokaisella sivulla on sama kuvaus, Googlella ei ole keinoa tietää, mikä tekee jokaisesta sivusta ainutlaatuisen ja arvokkaan. Kerro Googlelle, mistä on kyse, niin arvosi on parempi.

Anna Googlelle muutama viikko aikaa avata sivustosi uudelleen, ja sinun pitäisi nähdä parannuksia. SEO on pitkä peli, mutta nämä perusasiat ovat perusta, jolle kaikki muu rakentuu.

Lisää luettavaa

logo

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