Έχω τρέξει αυτό το blog για λίγο καιρό τώρα, γράφοντας λεπτομερή τεχνικά άρθρα για το ASP.NET Core, Πλαίσιο οντότητας, HTMX, και όλα τα είδη της καλοσύνης .NET. Ωστόσο, η κατάταξη αναζήτησής μου ήταν... underwhelming. Μετά από επιτέλους έρευνα γιατί, ανακάλυψα ότι είχα σαμποτάρει το δικό μου SEO με κάποια λάθη νεοσύλλεκτων.
Όταν η Google σέρνεται στο site σας, κοιτάζει αρκετά βασικά σήματα για να καταλάβει τι είναι κάθε σελίδα.
Άκου τι θα κάνω _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.">
Κάθε μία, σελίδα, η ίδια περιγραφή. Background Services in ASP.NET CoreΤην ίδια περιγραφή με την αρχική μου σελίδα. RAG Αρχιτεκτονική♪ Ίδια περιγραφή. ♪
Η Google βλέπει 200+ σελίδες με πανομοιότυπες περιγραφές και σκέφτεται "αυτή η ιστοσελίδα έχει αντίγραφα θεμάτων περιεχομένου" ή "αυτή η ιστοσελίδα δεν ενδιαφέρεται για την παροχή χρήσιμων πληροφοριών."
Έχω ένα πολύγλωσσο blog με μεταφράσεις. Το ίδιο περιεχόμενο υπάρχει σε:
/blog/my-article (Αγγλικά)/blog/my-article/fr (Γαλλικά)/blog/my-article/de (Γερμανικά)Χωρίς κανονικά URL, η Google μπορεί να τα δει ως διπλό περιεχόμενο, διαλύοντας την τιμή SEO σε πολλαπλά URL αντί να την εδραιώσει στην αρχική σελίδα.
Τα αποτελέσματα αναζήτησης της Google μπορούν να δείξουν πλούσια snippets - πληροφορίες συγγραφέα, να δημοσιεύσουν ημερομηνίες, προεπισκοπήσεις άρθρου. Δομημένα δεδομένα JSON-LD- Δεν ήμουν.
Κάθε σελίδα μοιράζεται το ίδιο og:image. Ενώ αυτό δεν επηρεάζει άμεσα τις κατατάξεις της Google, επηρεάζει τα ποσοστά κλικ-μέσω όταν τα άρθρα μοιράζονται στα μέσα κοινωνικής δικτύωσης - τα οποία επηρεάζουν έμμεσα SEO μέσω σημάτων εμπλοκής.
Η πρώτη διόρθωση ήταν κάνοντας τη διάταξη υποστήριξη δυναμική περιγραφές με μια λογική οπισθοδρόμηση:
@{
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 σελίδα.
Κάθε δημοσίευση blog παράγει τώρα την περιγραφή του από τους πρώτους 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}";
}
Τα βασικά σημεία εδώ:
og:type Στο "εμβόλιο" - Λέει κοινωνικές πλατφόρμες αυτό είναι ένα άρθρο, όχι μια γενική ιστοσελίδαΑυτό είναι το μεγάλο για πλούσια snippets. Προσθέστε ένα μπλοκ σεναρίου JSON-LD στο blog σας post view:
<!-- 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.
Αυτό το δομημένο στοιχείο λέει στο Google:
Η Google μπορεί να χρησιμοποιήσει αυτό για να δείξει πλούσια snippets στα αποτελέσματα αναζήτησης, συμπεριλαμβανομένων των πληροφοριών συγγραφέα, να δημοσιεύσει ημερομηνίες, και πολλά άλλα.
Μην ξεχάσετε τις στατικές σας σελίδες. Κάθε μία θα πρέπει να έχει μια μοναδική, σχετική περιγραφή:
<!-- 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.";
}
Εδώ είναι ένα απλό χειριστήριο που παράγει ένα δυναμικά:
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" });
Πείτε τις μηχανές αναζήτησης όπου το sitemap σας είναι και τι να συρθεί:
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 για κάθε δημοσίευση. Για ένα τεχνικό blog, πιθανότατα δεν αξίζει τον κόπο.
Μια συνεπής επώνυμη εικόνα είναι καλό για την αναγνώριση. Προσαρμοσμένη παραγωγή εικόνας θέματα περισσότερο για το οπτικό περιεχόμενο, ιστοσελίδες ειδήσεων, ή σελίδες μάρκετινγκ που ανταγωνίζονται για κλικ στο Facebook και το Twitter.
Αν ήθελες να τα δημιουργήσεις, θα μπορούσες να χρησιμοποιήσεις ImageSharp (την οποία ήδη χρησιμοποιώ για επεξεργασία εικόνας) για την επικάλυψη κειμένου σε ένα πρότυπο:
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";
}
Αλλά για ένα τεχνικό μπλογκ;
Χρήση The Google's Rich Results Test για την επικύρωση των δομημένων δεδομένων σας. Επικόλληση URL και θα σας πει αν JSON-LD σας είναι έγκυρη και ποια πλούσια αποτελέσματα είστε επιλέξιμοι για.
Η απλούστερη δοκιμή - δείτε την πηγή της σελίδας και ελέγξτε:
<meta name="description"> Μοναδικό για αυτή τη σελίδα;<link rel="canonical">?<script type="application/ld+json"> Μπλόκαρε;Μετά την ανάπτυξη, υποβάλλει sitemap σας σε Κονσόλα αναζήτησης GoogleName. Μπορείτε επίσης να χρησιμοποιήσετε το εργαλείο επιθεώρησης URL για να δείτε ακριβώς πώς η Google βλέπει τις σελίδες σας και να ζητήσει εκ νέου-δείκτη.
Οι διορθώσεις ήταν σαφείς:
Πρόβλημα Φτιάξε
|---------|-----|
Στατική μετα-περιγραφές Δυναμική ViewBag.Description Αυτόματη παραγωγή από το περιεχόμενο
Δεν έχει προστεθεί κανονική URL <link rel="canonical"> στη διάταξη □
Δεν υπάρχουν δομημένα δεδομένα JSON-LD BlogPosting schema σε κάθε θέση
Λείπει τα μεταδεδομένα του άρθρου article:published_time, article:author, article:tag meta tags
Περιγραφή Στατικής σελίδας Περιγραφή: Μοναδικές περιγραφές για το Σπίτι, Blog, Επικοινωνία, Αναζήτηση σελίδων
Η βασική διορατικότητα: Η Google δεν μπορεί να διαβάσει το μυαλό σου. Εάν κάθε σελίδα έχει την ίδια περιγραφή, η Google δεν έχει κανένα τρόπο να ξέρει τι κάνει κάθε σελίδα μοναδική και πολύτιμη. Πείτε στο Google τι κάθε σελίδα είναι περίπου, και θα βαθμολογήσετε καλύτερα.
Δώστε του μερικές εβδομάδες για το Google να ξανασυντρίψει το site σας, και θα πρέπει να δείτε βελτιώσεις. SEO είναι ένα μεγάλο παιχνίδι, αλλά αυτά τα βασικά είναι το θεμέλιο όλα τα άλλα βασίζεται σε.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.