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
Wednesday, 26 November 2025
Ik heb deze blog draaien voor een tijdje nu, het schrijven van gedetailleerde technische artikelen over ASP.NET Core, Entity Framework, HTMX, en allerlei .NET goodness. Toch mijn zoekrankings waren... underwhelming. Na eindelijk onderzoeken waarom, Ik ontdekte dat ik mijn eigen SEO saboteren met een aantal rookie fouten. Hier is wat verkeerd was en hoe ik het opgelost.
Wanneer Google kruipt uw site, het kijkt naar verschillende belangrijke signalen om te begrijpen waar elke pagina over gaat. Ik was het verzenden van Google dezelfde signalen voor elke pagina.
Hier is wat mijn _Layout.cshtml zag eruit als:
<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.">
Elke pagina, dezelfde beschrijving, mijn artikel over... Achtergronddiensten in ASP.NET Core♪ Dezelfde beschrijving als mijn homepage. Mijn diepe duik in RAG-architectuurDezelfde beschrijving.
Google ziet 200+ pagina's met identieke beschrijvingen en denkt "deze site heeft dubbele inhoud problemen" of "deze site geeft niet om het verstrekken van nuttige informatie." Hoe dan ook, rankings lijden.
Ik heb een meertalige blog met vertalingen. Dezelfde inhoud bestaat op:
/blog/my-article (Nederlands)/blog/my-article/fr (Frans)/blog/my-article/de (Duits)Zonder canonieke URL's zou Google deze kunnen zien als dubbele inhoud, waardoor de SEO-waarde wordt verdund over meerdere URL's in plaats van te consolideren op de primaire pagina.
De zoekresultaten van Google kunnen rijke knipsels tonen - auteur informatie, publiceren data, artikel previews. Maar alleen als u Google over deze gegevens te vertellen met behulp van JSON-LD gestructureerde gegevensDat was ik niet.
Elke pagina gedeeld hetzelfde og:image. Hoewel dit geen directe invloed heeft op Google-rankings, beïnvloedt het de click-through tarieven wanneer artikelen worden gedeeld op sociale media - die indirect invloed hebben op SEO door middel van engagement signalen.
De eerste oplossing was het maken van de layout ondersteuning dynamische beschrijvingen met een verstandige terugval:
@{
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" />
}
}
Nu leest de lay-out ViewBag.Description indien ingesteld, terugvallend op een standaard. De canonieke URL wordt automatisch ingesteld op de huidige pagina-URL.
Elke blog post genereert nu zijn beschrijving van de eerste 155 karakters van de inhoud. 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}";
}
De belangrijkste punten zijn:
og:type naar "artikel" - Vertelt sociale platforms dit is een artikel, geen generieke webpaginaDit is de grote voor rijke snippets. Voeg een JSON-LD scriptblok toe aan uw blog postweergave:
<!-- 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>
Let op de @@ ontsnapping voor de @ symbool in Razor views - JSON-LD maakt gebruik van @context en @type die anders zou worden geïnterpreteerd als Razor syntax.
Deze gestructureerde gegevens vertellen Google:
Google kan dit gebruiken om rijke snippets te tonen in zoekresultaten, waaronder auteur informatie, publiceren data, en nog veel meer.
Vergeet uw statische pagina's niet. Elk moet een unieke, relevante beschrijving hebben:
<!-- 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.";
}
Je hebt een sitemap nodig. Hier is een eenvoudige controller die er een dynamisch genereert:
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");
}
}
Registreer de route:
app.MapControllerRoute(
name: "sitemap",
pattern: "sitemap.xml",
defaults: new { controller = "SiteMap", action = "Index" });
Vertel zoekmachines waar uw sitemap is en wat te kruipen:
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);
});
Veel ontwikkelaars gebruiken RSS-lezers. Het hebben van een RSS-feed helpt ook bij de ontdekbaarheid:
<link rel="alternate" type="application/atom+xml"
title="RSS Feed for mostlylucid.net"
href="https://mostlylucid.net/rss" />
U kunt zich afvragen over het genereren van unieke OG-beelden voor elke post. Voor een technische blog, het is waarschijnlijk niet de moeite waard. Uw verkeer komt uit:
Een consistent merk beeld is prima voor herkenning. Custom beeldgeneratie is belangrijker voor visuele inhoud, nieuwssites, of marketing pagina's concurreren voor klikken op Facebook en Twitter.
Als je ze wilt genereren, kun je ze gebruiken. ImageSharp (die ik al gebruik voor beeldverwerking) om tekst op een sjabloon te overlayen:
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";
}
Maar voor een technisch blog?
Gebruik Google's Rich Results Test om uw gestructureerde gegevens te valideren. Plakken een URL en het zal u vertellen of uw JSON-LD geldig is en voor welke rijke resultaten u in aanmerking komt.
De eenvoudigste test - bekijk de paginabron en controleer:
<meta name="description"> uniek voor deze pagina?<link rel="canonical">?<script type="application/ld+json"> Blok?Na het implementeren, stuur uw sitemap naar Zoekconsole van Google. U kunt ook gebruik maken van de URL Inspectie tool om precies te zien hoe Google ziet uw pagina's en verzoeken opnieuw te indexeren.
De fixes waren eenvoudig:
Probleem Fix
|---------|-----|
Statische meta-beschrijvingen Dynamisch ViewBag.Description met auto-generatie van inhoud
Geen canonieke URL's toegevoegd <link rel="canonical"> naar lay-out
Geen gestructureerde gegevens JSON-LD BlogPosting schema voor elke post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ontbrekende artikel metadata article:published_time, article:author, article:tag meta-tags
Unieke beschrijvingen voor Home, Blog, Contact, Zoekpagina's
Het belangrijkste inzicht: Google kan je gedachten niet lezen. Als elke pagina dezelfde beschrijving heeft, heeft Google geen manier om te weten wat maakt elke pagina uniek en waardevol. Vertel Google waar elke pagina over gaat, en je zult beter rangschikken.
Geef het een paar weken voor Google om opnieuw te kruipen uw site, en je moet verbeteringen zien. SEO is een lang spel, maar deze fundamentele beginselen zijn de basis alles bouwt op.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.