# Warum Sie das nicht auf Google gefunden haben (und wie ich es behoben habe)

<!--category-- ASP.NET Core, SEO -->
<datetime class="hidden">2025-11-26T14:00</datetime>

Ich habe diesen Blog für eine Weile laufen, schreiben detaillierte technische Artikel auf ASP.NET Core, Entity Framework, HTMX, und alle Arten von .NET Güte. Doch meine Such-Rankings waren... Underwhelming. Nachdem ich schließlich untersucht, warum, Ich entdeckte, dass ich sabotiert meine eigenen SEO mit einigen Rookie-Fehler. Hier ist, was falsch war und wie ich es behoben.

[TOC]

# Das Problem: Jede Seite sah Identisch zu Google

Wenn Google krabbelt Ihre Website , es schaut auf mehrere Schlüsselsignale zu verstehen, was jede Seite ist . Ich war das Senden von Google die gleichen Signale für jede einzelne Seite .

## Die Rauchpistole: Statische Meta-Beschreibungen

Hier ist, was meine `_Layout.cshtml` sah aus wie:

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

Jeder. Single. Seite. Die gleiche Beschreibung. Mein Artikel über [Hintergrunddienste im ASP.NET Core](/blog/background-services-in-aspnetcore-part1)? Gleiche Beschreibung wie meine Homepage. [RAG-Architektur](/blog/rag-architecture)? Dieselbe Beschreibung.

Google sieht 200+ Seiten mit identischen Beschreibungen und denkt, "diese Website hat doppelte Inhalte Probleme" oder "diese Website kümmert sich nicht um die Bereitstellung nützlicher Informationen." So oder so, Rankings leiden.

## Fehlende kanonischen URLs

Ich habe einen mehrsprachigen Blog mit Übersetzungen. Der gleiche Inhalt existiert bei:

- `/blog/my-article` (Englisch)
- `/blog/my-article/fr` (Französisch)
- `/blog/my-article/de` (Deutsch)

Ohne kanonische URLs , Google könnte diese als doppelte Inhalte sehen , Verdünnen der SEO-Wert über mehrere URLs, anstatt sie auf der primären Seite zu konsolidieren .

## Keine strukturierten Daten

Googles Suchergebnisse können Rich Snippets zeigen - Autor Informationen, veröffentlichen Daten, Artikel Vorschauen. Aber nur, wenn Sie Google über diese Daten mit [JSON-LD strukturierte Daten](https://developers.google.com/search/docs/appearance/structured-data/article)Das war ich nicht.

## Statische soziale Bilder

Jede Seite hat das gleiche geteilt `og:image`. Während dies nicht direkt Google-Rankings beeinflussen, es Auswirkungen Klick-durch-Raten, wenn Artikel auf Social Media geteilt werden - die indirekt SEO durch Engagement-Signale beeinflusst.

# Die Korrekturen

## 1. Dynamische Meta-Beschreibungen

Der erste Fix war die Erstellung der Layout-Unterstützung dynamische Beschreibungen mit einem sinnvollen Fallback:

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

Jetzt liest sich das Layout `ViewBag.Description` wenn gesetzt, fällt auf eine Standardeinstellung zurück. Die kanonische URL wird automatisch auf die aktuelle Seiten-URL gesetzt.

## 2. Auto-generierte Beschreibungen für Blog-Posts

Jeder Blog-Post erzeugt nun seine Beschreibung aus den ersten 155 Zeichen des Inhalts. `Post.cshtml`:

```html
@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}";
}
```

Die wichtigsten Punkte hier:

1. **An der Wortgrenze abschneiden** - Wir finden den letzten Platz vor 155 Zeichen, um das Schneiden von Wörtern in der Hälfte zu vermeiden
2. **Neue Linien abstreifen** - Meta-Beschreibungen sollten einzeilig sein
3. **Satz `og:type` zu "Artikel"** - Sagt sozialen Plattformen Dies ist ein Artikel, nicht eine generische Webseite
4. **Artikel-Metadaten übergeben** - Veröffentlichtes Datum und Kategorien fließen durch das Layout

## 3. JSON-LD Strukturierte Daten

Dies ist die große für reiche Schnipsel. Fügen Sie einen JSON-LD-Skriptblock zu Ihrer Blog-Post-Ansicht hinzu:

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

Bemerken Sie die `@@` die Flucht nach der `@` Symbol in Razor-Ansichten - JSON-LD verwendet `@context` und `@type` die sonst als Razor-Syntax interpretiert würde.

Diese strukturierten Daten sagt Google:

- **Welche Art von Inhalten das ist** (Blogposting)
- **Wer hat es geschrieben?** (Person mit einer URL, um mehr zu erfahren)
- **Als es veröffentlicht und geändert wurde**
- **Welche Themen es behandelt** (Stichworte aus Kategorien)
- **Wie lange ist es?** (Wortzählen)
- **Was für eine Sprache ist das?**

Google kann dies verwenden, um reiche Schnipsel in Suchergebnissen zu zeigen, einschließlich Autoreninformationen, Veröffentlichung von Daten, und vieles mehr.

## 4. Einzigartige Beschreibungen für Schlüsselseiten

Vergessen Sie nicht Ihre statischen Seiten. Jeder sollte eine einzigartige, relevante Beschreibung haben:

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

# Sonstige SEO-Grundlagen

## Seiten

Sie benötigen eine Sitemap. Hier ist eine einfache Steuerung, die dynamisch erzeugt:

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

Registrieren Sie die Route:

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

## robots.txt

Sagen Sie Suchmaschinen, wo Ihre Sitemap ist und was zu kriechen:

```csharp
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-Feed

Viele Entwickler verwenden RSS-Reader. Mit einem RSS-Feed hilft auch bei der Auffindbarkeit:

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

# Was ist mit Bildern?

Vielleicht fragen Sie sich, wie Sie einzigartige OG-Bilder für jeden Beitrag. Für einen technischen Blog, es ist wahrscheinlich nicht die Mühe wert. Ihr Verkehr kommt aus:

- **Google-Suche** - Beschreibungen sind wichtiger als Bilder
- **RSS-Feeds** - keine Bilder
- **Hacker Nachrichten / Reddit** - Vorschaubilder kaum sichtbar
- **Direkte Verbindungen** - Entwickler teilen URLs

Ein konsequentes Markenbild ist gut für die Wiedererkennung. Kundenspezifische Bildgenerierung zählt mehr für visuelle Inhalte, News-Websites oder Marketing-Seiten, die um Klicks auf Facebook und Twitter konkurrieren.

Wenn du sie erzeugen wolltest, könntest du sie benutzen. [ImageSharp](https://docs.sixlabors.com/articles/imagesharp/) (die ich bereits für die Bildverarbeitung verwende) um Text auf einer Vorlage zu überlagern:

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

Aber für einen technischen Blog? Schiff, was du hast.

# Testen Sie Ihre SEO

## Google Rich Ergebnisse Test

Verwendung [Googles Rich Results Test](https://search.google.com/test/rich-results) um Ihre strukturierten Daten zu validieren. Fügen Sie eine URL ein und es wird Ihnen sagen, ob Ihre JSON-LD gültig ist und für welche reichen Ergebnisse Sie in Frage kommen.

## Seitenquelle anzeigen

Der einfachste Test - Ansicht der Seitenquelle und überprüfen:

- Ist die `<meta name="description">` einzigartig für diese Seite?
- Gibt es eine `<link rel="canonical">`?
- Gibt es eine `<script type="application/ld+json">` Blockieren?

## Google-Konsole durchsuchen

Geben Sie nach der Bereitstellung Ihre Sitemap an [Google-Konsole durchsuchen](https://search.google.com/search-console). Sie können auch das URL Inspection Tool verwenden, um genau zu sehen, wie Google Ihre Seiten sieht und Re-Indexierung anfordert.

# Zusammenfassung

Die Korrekturen waren einfach:

Problembehebung
|---------|-----|
Statische Meta-Beschreibungen, dynamisch `ViewBag.Description` mit automatischer Generierung von Inhalten
Keine kanonischen URLs hinzugefügt `<link rel="canonical">` zum Layout
Keine strukturierten Daten. JSON-LD `BlogPosting` Schema auf jedem Beitrag
Fehlende Artikel-Metadaten `article:published_time`, `article:author`, `article:tag` Meta-Tags
• Statische Seitenbeschreibungen • Einzigartige Beschreibungen für Zuhause, Blog, Kontakt, Suchseiten •

Die wichtigsten Erkenntnisse: **Google kann Ihre Gedanken nicht lesen**. Wenn jede Seite hat die gleiche Beschreibung , Google hat keine Möglichkeit zu wissen, was macht jede Seite einzigartig und wertvoll . Sagen Sie Google, was jede Seite ist , und Sie werden besser rangieren .

Geben Sie es ein paar Wochen für Google zu re-crawl Ihre Website, und Sie sollten Verbesserungen sehen. SEO ist ein langes Spiel, aber diese Grundlagen sind die Grundlage alles andere baut auf.

# Literaturhinweise

- [Googles SEO Starter Guide](https://developers.google.com/search/docs/fundamentals/seo-starter-guide)
- [Schema.org BlogPosting](https://schema.org/BlogPosting)
- [Graphenprotokoll öffnen](https://ogp.me/)
- [Google Rich Ergebnisse Test](https://search.google.com/test/rich-results)
- [Google-Konsole durchsuchen](https://search.google.com/search-console)