Mert Özen Further With Every Line
SEO & GEO SEO Fundamentals — #04

Appearing in Results: Title Links, Snippets, Meta Descriptions and Site Names

Mert Özen Nov 8, 2026 9 min 1 views
Appearing in Results: Title Links, Snippets, Meta Descriptions and Site Names

Google builds the result heading not only from the title but also from h1, og:title and link text; it takes the snippet mostly from page content, and meta descriptions have no length limit. We look at per-page title and meta in Razor and Angular 22 with real server output, snippet controls, site name and favicon rules, and the title suffix on mertozen.com.

Version note: This post is based on Google's title link, snippet, site name and favicon documentation as of November 2026; the code examples were built and run with .NET 10 and Angular 22.2.

Guess First

The <title> of Bookstore's book page is: Async in Practice | Bookstore. The meta description is the book's one-sentence summary. A reader searches Google for "async cancellation backpressure" and your page shows up. Question: is the blue heading in the result exactly your <title>? Is the gray text under it always the meta description? And if you don't cut the meta description at 155 characters, does Google penalize the page?

Google calls the clickable heading in a result a title link and generates it automatically. According to the documentation, these are the sources it uses: the content of the <title> element, the main visual title shown on the page, heading elements such as <h1>, the og:title meta tag, other text made large and prominent through styling, other text on the page, anchor text on the page, the text of links pointing to the page, and WebSite structured data. So <title> is the strongest candidate, but not the only one.

When does Google change the title? The examples in the documentation look a lot like bugs a developer produces with their own code:

  • Half-empty title: part of the title is missing, for example only | Bookstore is left because a template variable came back empty.
  • Obsolete title: on a recurring page the title keeps an old date while the visible heading shows the new one.
  • Inaccurate title: the <title> doesn't match the content.
  • Micro-boilerplate: many pages share the same title; Google may find a distinguishing detail on the page and add it.
  • No clear main title: several large headings look equally important; Google may use the first one.
  • Language or writing system mismatch: the title's language differs from the language of the page's main content.

Google's recommendations follow from that: give every page a descriptive and concise <title>, avoid vague labels like "Home" and keyword stuffing, make every title distinct, add the site name at the start or end with a delimiter such as a hyphen, colon or pipe, and make the main heading the most prominent text on the page. There is no length limit for titles; title links are truncated as needed, typically to fit the device width. There is also no way to change a title link by hand: you fix the page, Google recrawls and reprocesses it, which can take from a few days to a few weeks.

Per-Page Title and Description in Razor

In ASP.NET Core Razor the simplest way is for every page to write its own values into ViewData and for the layout to print them in one place. Bookstore's layout:

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] | Bookstore</title>
    @if (ViewData["Description"] is string description)
    {
        <meta name="description" content="@description" />
    }
    @if (ViewData["Canonical"] is string canonical)
    {
        <link rel="canonical" href="@canonical" />
    }
    @await RenderSectionAsync("Head", required: false)
</head>
@page "/books/{slug}"
@model Bookstore.Web.Pages.Books.DetailModel
@{
    ViewData["Title"] = Model.Book.Title;
    ViewData["Description"] = Model.Book.Summary;
    ViewData["Canonical"] = $"https://bookstore.example/books/{Model.Book.Slug}";
}

Real output:

$ curl -s http://localhost:5481/books/async-in-practice | grep -E "<title>|description|canonical"
    <title>Async in Practice | Bookstore</title>
        <meta name="description" content="Real-world async/await in .NET, from cancellation to backpressure." />
        <link rel="canonical" href="https://bookstore.example/books/async-in-practice" />

There are two small decisions here. If there's no description we don't print the tag at all; an empty content="" tells Google nothing. The site name is fixed in the layout: pages only provide their own title, so a "half-empty title" can only happen if the page title comes back empty. Adding a check that breaks the build or a test when Title is empty is cheap insurance against that.

Route-Based Title and Meta in Angular

In Angular, the Router's title property, a TitleStrategy and the Meta service do the same job. The strategy that adds the site name in one place:

import { Injectable, inject } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
import { RouterStateSnapshot, TitleStrategy } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class BookstoreTitleStrategy extends TitleStrategy {
  private readonly title = inject(Title);

  override updateTitle(snapshot: RouterStateSnapshot): void {
    const pageTitle = this.buildTitle(snapshot);
    this.title.setTitle(pageTitle ? `${pageTitle} | Bookstore` : 'Bookstore');
  }
}

@Injectable({ providedIn: 'root' })
export class PageMeta {
  private readonly meta = inject(Meta);

  setDescription(description: string): void {
    this.meta.updateTag({ name: 'description', content: description });
  }
}
const bookTitle: ResolveFn<string> = route => books[route.paramMap.get('slug')!]?.title ?? 'Book not found';

@Component({
  selector: 'app-book-page',
  template: `<h1>{{ book?.title }}</h1><p>{{ book?.summary }}</p>`,
})
export class BookPage implements OnInit {
  private readonly route = inject(ActivatedRoute);
  private readonly pageMeta = inject(PageMeta);

  readonly book = books[this.route.snapshot.paramMap.get('slug')!];

  ngOnInit(): void {
    this.pageMeta.setDescription(this.book?.summary ?? 'Books for developers.');
  }
}

export const routes: Routes = [
  { path: 'books/:slug', component: BookPage, title: bookTitle },
];

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    { provide: TitleStrategy, useClass: BookstoreTitleStrategy },
  ],
};

The part of this code that matters shows up on the server, not in the browser. I rendered two URLs (Uniform Resource Locator) on the server with the renderApplication function from @angular/platform-server and filtered the lines in the head:

$ node dist/render.js /books/async-in-practice
Angular is running in development mode.
<title>Async in Practice | Bookstore</title>
<meta name="description" content="Real-world async/await in .NET, from cancellation to backpressure.">
<h1>Async in Practice</h1>

$ node dist/render.js /books/unknown-book
Angular is running in development mode.
<title>Book not found | Bookstore</title>
<meta name="description" content="Books for developers.">
<h1></h1>

The first URL is exactly what we want. The second is a warning: a title and description are produced for the missing book too, and unless you set the status code separately this page returns 200. In Google's eyes that is a soft 404 candidate. To return a real 404 you need to set the response status in Angular SSR (Server-Side Rendering); that, and choosing a RenderMode, are covered in the Technical SEO (Search Engine Optimization) series, in the post "SEO with Angular SSR". If the app runs only with CSR (Client-Side Rendering), the title and description aren't in the initial HTML (HyperText Markup Language); Google only sees them at the rendering stage.

Snippets and Meta Descriptions

The snippet is the description under the title link. Google generates it automatically and primarily from the page's content; the goal is to preview the part that best matches the user's search. That's why the same page can show different snippets for different queries. Google uses the meta description when it gives "a more accurate description of the page" than the page content does.

The answer to the third opening question: there is no length limit for meta descriptions, and no penalty either. Snippets are truncated as needed to fit the device width. What Google recommends is not a length but content: write a description unique to each page, add useful details such as the author, publish date or price, and write a readable summary, not a keyword list. Using site-level descriptions for the home page and list pages is reasonable.

There is an interesting detail in mertozen.com's source code: a blog post's description is produced by taking the first 157 characters of the summary and appending "…". That's not a violation, but Google's documentation doesn't require any such cut, and the cut can fall in the middle of a word. If the summary is already written short and answer-like, giving it in full and leaving the truncation to Google is the cleaner option.

Snippet Controls: nosnippet, max-snippet and data-nosnippet

You can decide what must not appear in the snippet. The nosnippet robots rule turns off the text snippet and video preview for the page. max-snippet:[number] limits the snippet to at most that many characters; 0 is the same as nosnippet, -1 leaves the length to Google. To hide only part of the page there is the data-nosnippet attribute, and it works only on span, div and section elements:

<section id="stock">
    <div data-nosnippet>Only 2 copies left, offer ends Friday.</div>
</section>

Bookstore's stock and campaign text goes stale fast; we don't want it in the snippet a week later. A note: according to Google's robots meta tag documentation, nosnippet and max-snippet now also apply to Google's AI (Artificial Intelligence) features AI Overviews and AI Mode; they prevent or limit the content from being used as direct input to those features. We'll cover the controls for AI features in detail in the GEO (Generative Engine Optimization) series.

In April 2026 a section on "read more" deep links was added to the snippet documentation: to make it easier for Google to show a link from the snippet to the relevant part of the page, it recommends not hiding content behind expandable panels and tabs, not forcing the scroll position with JavaScript on page load, and not removing the hash fragment from the URL during load.

Site Names and Favicons

In a result, the site name and favicon appear above the title link. Google generates the site name automatically from the home page and from references across the web; the strongest way to state your preferred name is the name property of the WebSite structured data on the home page, while alternateName lists fallbacks in order of preference. Google also looks at og:site_name, the <title> and headings on the home page. Site names are supported at domain and subdomain level; they aren't supported for subdirectories. So the English section under /en/ can't get its own site name. The markup details are in the upcoming Structured Data and Schema.org series.

The favicon rules are short: square (1:1) and at least 8x8 pixels, but larger than 48x48 is recommended so it looks good on various surfaces; rel="icon" is the preferred value. There's one favicon per hostname: subdomains can have their own, subdirectories share the domain's. Don't change the favicon URL often; recrawling can take from a few days to a few weeks. In August 2026 Google explicitly listed the supported favicon file formats in the documentation; the support itself didn't change.

The " — Mert Özen" Suffix on mertozen.com

Every page title on this site ends with the same suffix: on a blog post, {post title} — Mert Özen. Google's recommendation is to add the site name at the start or end with a delimiter; its examples are a hyphen, a colon and a pipe. The em dash isn't in the example list, but the documentation gives "examples" of delimiters, not a strict list. The more important detail: Google writes that if it already shows the domain-level site name in the result, it may drop a repeated site name from the title link. So the suffix most likely doesn't lengthen the heading the reader sees, but the decision is Google's.

Writing the TR and EN titles separately is also the right decision. In the source code the title comes from the CMS (Content Management System) by language: a Turkish title on the TR page, an English title on the page under /en/. The "language or writing system mismatch" in Google's list is exactly what happens when a Turkish title is left on an English page.

Production Notes

Test the title and description in the template. An integration test can walk all routes and catch empty or duplicate <title>s. The most common cause of micro-boilerplate is pagination or filter pages carrying the same title as the main list.

If you use SSR or prerendering, check the head in the server output, not in the browser. The browser tab title shows the state after JavaScript ran, not what Google sees in the initial HTML.

Learn to wait for title link changes. According to Google's note, reprocessing takes days, sometimes weeks; measuring after one day and rolling back leads to the wrong conclusion.

Common Pitfalls

Turning the title into a keyword list. A title like "Async, Await, C#, .NET, Async Book" gets close to keyword stuffing and also raises the chance that Google rewrites it.

Writing meta descriptions by counting characters. There's no limit; the real question is whether the description describes the page more accurately than the content does.

Putting the same description on every page. Google then has no reason to use the description and generates the snippet from the content.

Using data-nosnippet on the wrong element. It doesn't work on p or custom elements; wrap the content in a div, span or section.

A Small Experiment

★☆☆ Fetch the <title> and meta description of five pages on your own site with curl. Are any of them identical?

★★☆ Add a check to the Razor layout that throws an exception in development when ViewData["Title"] is empty and prints the site name alone in production.

★★★ In the Angular example, make the TitleStrategy leave an empty title instead of "Book not found" for a missing book, then show with server output why that turns into a "half-empty title" problem, and apply the right fix (a 404).

We can now control how we look on the results page. In the next post we start measuring whether we show up at all: setting up Google Search Console and Bing Webmaster Tools, URL Inspection and the core reports.