Ho eseguito questo blog per un po 'di tempo, scrivendo articoli tecnici dettagliati su ASP.NET Core, Entity Framework, HTMX, e tutti i tipi di .NET bontà. Eppure le mie classifiche di ricerca erano... sottovalutante. Dopo aver finalmente indagato perché, ho scoperto che ero stato sabotando il mio SEO con alcuni errori di novellino. Ecco cosa era sbagliato e come ho risolto.
Quando Google striscia il tuo sito, guarda diversi segnali chiave per capire che cosa ogni pagina è circa. Stavo inviando Google gli stessi segnali per ogni singola pagina.
Ecco cosa _Layout.cshtml sembrava:
<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.">
Ogni. Single. Pagina. La stessa descrizione. Il mio articolo su Servizi di background in ASP.NET Core? Stessa descrizione della mia homepage. La mia immersione profonda in RAG ArchitetturaStessa descrizione.
Google vede oltre 200 pagine con descrizioni identiche e pensa "questo sito ha problemi di contenuti duplicati" o "questo sito non si preoccupa di fornire informazioni utili." In entrambi i casi, le classifiche soffrono.
Ho un blog multilingue con traduzioni. Lo stesso contenuto esiste in:
/blog/my-article (Inglese)/blog/my-article/fr (francese)/blog/my-article/de (Tedesco)Senza URL canonici, Google potrebbe vederli come contenuti duplicati, diluendo il valore SEO su più URL invece di consolidarlo sulla pagina primaria.
I risultati di ricerca di Google possono mostrare snippet ricchi - informazioni sull'autore, date di pubblicazione, anteprime dell'articolo. Ma solo se si dice a Google su questi dati utilizzando Dati strutturati JSON-LD- Non lo ero.
Ogni pagina condivideva lo stesso og:image. Anche se questo non influenza direttamente le classifiche di Google, ha un impatto sui tassi di click-through quando gli articoli sono condivisi sui social media - che influenza indirettamente SEO attraverso segnali di engagement.
La prima correzione è stata quella di rendere la descrizione dinamica del supporto al layout con un ripiego sensibile:
@{
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" />
}
}
Ora il layout legge ViewBag.Description se impostato, rientrando a un valore predefinito. L'URL canonico è impostato automaticamente all'URL della pagina corrente.
Ogni post del blog ora genera la sua descrizione dai primi 155 caratteri dei contenuti. 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}";
}
I punti chiave qui:
og:type a "articolo" - Racconta alle piattaforme sociali questo è un articolo, non una pagina web genericaQuesto è quello grande per snippet ricchi. Aggiungi un blocco di script JSON-LD alla tua vista post sul blog:
<!-- 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>
Notare la @@ scappare per il @ simbolo nelle viste rasoio - JSON-LD utilizza @context e @type che altrimenti sarebbe interpretato come sintassi Razor.
Questi dati strutturati dicono a Google:
Google può utilizzare questo per mostrare snippet ricchi nei risultati di ricerca, comprese le informazioni dell'autore, pubblicare le date, e altro ancora.
Non dimenticate le vostre pagine statiche. Ognuno dovrebbe avere una descrizione unica e pertinente:
<!-- 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.";
}
Hai bisogno di una mappa del sito. Ecco un semplice controller che genera dinamicamente:
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");
}
}
Registrare il percorso:
app.MapControllerRoute(
name: "sitemap",
pattern: "sitemap.xml",
defaults: new { controller = "SiteMap", action = "Index" });
Dite ai motori di ricerca dove si trova la mappa del sito e cosa strisciare:
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);
});
Molti sviluppatori utilizzano lettori RSS. Avere un feed RSS aiuta anche con la scoperta:
<link rel="alternate" type="application/atom+xml"
title="RSS Feed for mostlylucid.net"
href="https://mostlylucid.net/rss" />
Potreste chiedervi circa generare le immagini uniche di OG per ogni post. Per un blog tecnico, probabilmente non vale la pena lo sforzo. Il vostro traffico viene da:
Un'immagine di marca coerente va bene per il riconoscimento. La generazione di immagini personalizzate conta di più per i contenuti visivi, i siti di notizie, o le pagine di marketing in competizione per i clic su Facebook e Twitter.
Se volessi generarli, potresti usare Condivisione immagini (che uso già per l'elaborazione delle immagini) per sovrapporre il testo su un modello:
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";
}
Ma per un blog tecnico? Spedisci quello che hai.
Uso Test dei risultati ricchi di Google per convalidare i tuoi dati strutturati. Incolla un URL e ti dirà se il tuo JSON-LD è valido e quali risultati ricchi puoi ottenere.
Il test più semplice - visualizzare la sorgente della pagina e controllare:
<meta name="description"> unico per questa pagina?<link rel="canonical">?<script type="application/ld+json"> Blocco?Dopo l'implementazione, inviare la mappa del sito a Console di ricerca di Google. È inoltre possibile utilizzare lo strumento URL Inspection per vedere esattamente come Google vede le pagine e richiedere la riindicizzazione.
Le correzioni erano semplici:
| Problema | Corretto |
|---|---|
| Metadescrizioni statiche | Dinamica ViewBag.Description con autogenerazione dal contenuto |
| Nessun URL canonico | Aggiunto <link rel="canonical"> al layout |
| Nessun dato strutturato | JSON-LD BlogPosting schema su ciascun palo |
| Metadata dell'articolo mancante | article:published_time, article:author, article:tag meta tag |
| Descrizioni statiche delle pagine | Descrizioni uniche per la casa, Blog, Contatto, pagine di ricerca |
L'intuizione chiave: Google non può leggere la tua mente. Se ogni pagina ha la stessa descrizione, Google non ha modo di sapere cosa rende ogni pagina unica e preziosa. Dite a Google che cosa ogni pagina è circa, e si classifica meglio.
Dategli alcune settimane per Google per ri-crawl il tuo sito, e si dovrebbe vedere miglioramenti. SEO è un gioco lungo, ma questi fondamentali sono la base tutto il resto si basa su.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.