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ä.
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.
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".
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.
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.
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.
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.
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:
og:type "hiukkanen" - Kertoo sosiaalisille alustoille, että kyseessä on artikkeli, ei yleinen verkkosivuLisää 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:
Google voi käyttää tätä näyttääkseen runsaasti hakutuloksia, mukaan lukien tekijän tiedot, julkaistakseen päivämääriä ja paljon muuta.
Ä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.";
}
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" });
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);
});
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" />
Saatat miettiä ainutlaatuisten OG-kuvien tuottamista jokaiseen postaukseen. Teknisessä blogissa se ei todennäköisesti ole vaivan arvoista. Liikenne tulee:
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.
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.
Yksinkertaisin testi - katso sivun lähde ja tarkista:
<meta name="description"> ainutlaatuista tälle sivulle?<link rel="canonical">?<script type="application/ld+json"> Block?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.
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.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.