# Varför du inte hittade detta på Google (och hur jag fixade det)

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

Jag har kört den här bloggen ett tag nu, skriva detaljerade tekniska artiklar om ASP.NET Core, Entity Framework, HTMX, och alla typer av .NET godhet. Ändå min sökning ranking var ... underwhelming. Efter att äntligen undersöka varför, upptäckte jag att jag hade sabotera min egen SEO med några nybörjar misstag. Här är vad som var fel och hur jag fixade det.

[TOC]

# Problemet: Varje sida såg identisk till Google

När Google kryper din webbplats, det tittar på flera viktiga signaler för att förstå vad varje sida handlar om. Jag skickade Google samma signaler för varje enskild sida.

## Rökpistolen: Statisk Meta Beskrivningar

Här är vad min `_Layout.cshtml` såg ut som:

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

Varje, singel, sida, samma beskrivning, min artikel om [Bakgrundstjänster i ASP.NET Core](/blog/background-services-in-aspnetcore-part1)? Samma beskrivning som min hemsida. Min djupdykning i [RAG-arkitektur](/blog/rag-architecture)- Samma beskrivning.

Google ser 200+ sidor med identiska beskrivningar och tänker "den här webbplatsen har duplicerade innehållsproblem" eller "den här webbplatsen bryr sig inte om att ge användbar information." Oavsett, ranking lider.

## Saknar Canoniska webbadresser

Jag har en flerspråkig blogg med översättningar. Samma innehåll finns på:

- `/blog/my-article` (engelska)
- `/blog/my-article/fr` (Franska)
- `/blog/my-article/de` (tyska)

Utan kanoniska webbadresser kan Google se dessa som duplicerat innehåll och späda ut SEO-värdet över flera webbadresser istället för att konsolidera det på den primära sidan.

## Inga strukturerade data

Googles sökresultat kan visa rika snippets - författare information, publicera datum, artikel förhandsvisningar. Men bara om du berättar för Google om denna data med hjälp av [Strukturerade JSON-LD-uppgifter](https://developers.google.com/search/docs/appearance/structured-data/article)- Det gjorde jag inte.

## Statiska sociala bilder

Varje sida delade samma `og:image`. Även om detta inte direkt påverkar Google ranking, det påverkar klick-genom-hastigheter när artiklar delas på sociala medier - som indirekt påverkar SEO genom engagemang signaler.

# Rättelserna

## 1. Dynamiska Metabeskrivningar

Den första fixen var att göra layouten stöd dynamiska beskrivningar med en vettig reserv:

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

Nu läser layouten `ViewBag.Description` Om inställd, återgår till en standard. Den kanoniska webbadressen ställs automatiskt in till den aktuella sidans webbadress.

## 2. Auto-Generated Beskrivningar för blogginlägg

Varje blogginlägg genererar nu sin beskrivning från de första 155 tecken på innehåll. `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}";
}
```

De viktigaste punkterna här:

1. **Trunkera vid ordgräns** - Vi hittar det sista utrymmet innan 155 tecken för att undvika att klippa ord på mitten
2. **Ta bort nya linjer** - Metabeskrivningarna ska vara enradiga
3. **Ställ in `og:type` till "artikel"** - Berättar sociala plattformar detta är en artikel, inte en allmän hemsida
4. **Genomför artikelmetadata** - Publicerat datum och kategorier flödar fram till layouten

## 3. JSON-LD-strukturerade data

Detta är den stora för rika snippets. Lägg till en JSON-LD skriptblock till din blogg inläggsvy:

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

Lägg märke till `@@` fly för `@` symbol i Razor-vyer - JSON-LD användningar `@context` och `@type` som annars skulle tolkas som Razor syntax.

Denna strukturerade data berättar för Google:

- **Vilken typ av innehåll detta är** (BlogPost)
- **Vem skrev det?** (Person med en webbadress för att lära sig mer)
- **När det publicerades och ändrades**
- **Vilka ämnen behandlas i meddelandet?** (nyckelord från kategorier)
- **Hur länge är det?** (ordfel)
- **Vilket språk det är på**

Google kan använda detta för att visa rika snippets i sökresultat, inklusive författarinformation, publicera datum och mycket mer.

## 4. Unika beskrivningar för nyckelsidor

Glöm inte dina statiska sidor. Var och en ska ha en unik, relevant beskrivning:

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

# Andra SEO Essentials

## Webbkarta

Du behöver en sitemap. Här är en enkel controller som genererar en dynamisk:

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

Registrera rutten:

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

## robotar.txt

Berätta för sökmotorer var din sitemap är och vad du ska krypa:

```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- kanalName

Många utvecklare använder RSS-läsare. Att ha ett RSS-flöde hjälper också till att upptäcka:

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

# Hur är det då med bilder?

Du kanske undrar om generera unika OG bilder för varje inlägg. För en teknisk blogg, är det förmodligen inte värt ansträngningen. Din trafik kommer från:

- **Google- sökning** - beskrivningar betyder mer än bilder
- **RSS-flöden** - inga bilder
- **Hacker Nyheter / Reddit** - miniatyrbilder knappt synliga
- **Direktlänkar** - utvecklare som delar webbadresser

En konsekvent märkesbild är bra för igenkänning. Anpassad bildgenerering betyder mer för visuellt innehåll, nyhetssidor eller marknadsföringssidor som konkurrerar om klick på Facebook och Twitter.

Om du ville generera dem, kunde du använda [AvbildaSharp](https://docs.sixlabors.com/articles/imagesharp/) (som jag redan använder för bildbehandling) för att lägga över text på en mall:

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

Men för en teknisk blogg? Ship vad du har.

# Testa din SEO

## Testa Google Rich Results

Användning [Googles rika resultat test](https://search.google.com/test/rich-results) för att validera din strukturerade data. Klistra in en URL och det kommer att berätta om din JSON-LD är giltig och vilka rika resultat du är berättigad till.

## Visa sidkälla

Det enklaste testet - visa sidans källa och kontrollera:

- Är `<meta name="description">` unik för denna sida?
- Finns det en `<link rel="canonical">`?
- Finns det en `<script type="application/ld+json">` Blockera?

## Google Sökkonsol

Efter utplacering, skicka in din webbplatskarta till [Google Sökkonsol](https://search.google.com/search-console). Du kan också använda URL Inspection tool för att se exakt hur Google ser dina sidor och begära omindexering.

# Sammanfattning

Rättelserna var enkla:

Problemet är att rätta till det.
|---------|-----|
till Statiska metabeskrivningar  på Dynamiska `ViewBag.Description` med automatisk generering från innehåll
Inga kanoniska webbadresser tillagda `<link rel="canonical">` till layout
Inga strukturerade data  på JSON-LD `BlogPosting` schema på varje post  till
till Saknar artikelmetadata `article:published_time`, `article:author`, `article:tag` metataggar
till Statiska sidbeskrivningar  och unika beskrivningar för Hem, Blogg, Kontakt, Söksidor

Den viktigaste insikten: **Google kan inte läsa dina tankar**. Om varje sida har samma beskrivning, Google har inget sätt att veta vad som gör varje sida unik och värdefull. Berätta för Google vad varje sida handlar om, och du kommer ranka bättre.

Ge det några veckor för Google att re-crawl din webbplats, och du bör se förbättringar. SEO är ett långt spel, men dessa grundläggande är grunden allt bygger på.

# Ytterligare läsning

- [Googles SEO Starter Guide](https://developers.google.com/search/docs/fundamentals/seo-starter-guide)
- [Schema.org BloggPostning](https://schema.org/BlogPosting)
- [Öppna diagramprotokoll](https://ogp.me/)
- [Testa Google Rich Results](https://search.google.com/test/rich-results)
- [Google Sökkonsol](https://search.google.com/search-console)