A title can be 64 characters and excellent—or 42 characters and useless. Search results truncate to the available display width, not a universal character count, and Google can generate a different title link from the page heading, prominent text, links, or other metadata. The practical job is to find titles that fail users, then fix the system that produced them.

What deserves attention first

  • Missing or empty <title> elements.

  • Duplicate titles across different canonical pages.

  • Titles whose unique words appear after a long shared boilerplate prefix.

  • Keyword repetition, obsolete dates, inaccurate promises, or mismatched language.

  • Titles inconsistent with the main visible heading and page purpose.

  • Important pages with impressions but weak clicks, frequent title-link rewrites, or truncation that hides the differentiator.

  • The HTML <title> labels the document in browser tabs and is an important source for search title links.

  • The <h1> is the visible main heading and may be longer or more conversational.

  • Open Graph and social titles serve sharing contexts and can differ intentionally.

  • Google automatically chooses a title link from multiple page and link signals; the source <title> is a preference, not a command.

  • Keep these signals aligned on subject and intent even when their exact wording differs.

1. Start with a canonical URL inventory

SEO audit workspacebash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install requests beautifulsoup4
A project-local Python environment is created and the HTTP/HTML parsing dependencies are installed.

Keep the audit isolated and reproducible

  • A virtual environment avoids changing system Python packages.

  • requests fetches HTTP responses; Beautiful Soup parses HTML without brittle regex extraction.

  • Record dependency versions in a lock/requirements file for scheduled audits.

  • Do not run an aggressive crawler against infrastructure you do not own or have permission to test.

  • A production crawl needs explicit rate, timeout, retry, authentication, and robots/policy decisions.

Use the XML sitemap as the initial list when it represents indexable canonical pages. Supplement it with CMS exports, analytics, Search Console/Bing data, logs, and a link crawl so orphaned or accidentally omitted URLs are not invisible.

2. Crawl titles without silently rewriting content

audit_titles.pypython
from __future__ import annotations
 
import csv
import time
import xml.etree.ElementTree as ET
from collections import Counter
from dataclasses import dataclass
from urllib.parse import urljoin, urlparse
 
import requests
from bs4 import BeautifulSoup
 
SITEMAP = "https://example.com/sitemap.xml"
OUTPUT = "title-audit.csv"
TIMEOUT_SECONDS = 15
DELAY_SECONDS = 0.25
USER_AGENT = "ExampleTitleAudit/1.0 (+seo@example.com)"
 
@dataclass
class PageTitle:
    requested_url: str
    final_url: str
    status: int | None
    canonical: str
    title: str
    h1: str
    error: str = ""
 
def normalized(text: str) -> str:
    return " ".join(text.split()).casefold()
 
def load_sitemap(session: requests.Session) -> list[str]:
    response = session.get(SITEMAP, timeout=TIMEOUT_SECONDS)
    response.raise_for_status()
    root = ET.fromstring(response.content)
    namespace = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
    return [node.text.strip() for node in root.findall(".//sm:loc", namespace) if node.text]
 
def inspect(session: requests.Session, url: str) -> PageTitle:
    try:
        response = session.get(url, timeout=TIMEOUT_SECONDS, allow_redirects=True)
        response.raise_for_status()
        if "text/html" not in response.headers.get("content-type", "").lower():
            return PageTitle(url, response.url, response.status_code, "", "", "", "not HTML")
 
        soup = BeautifulSoup(response.text, "html.parser")
        title = soup.title.get_text(" ", strip=True) if soup.title else ""
        h1 = soup.find("h1")
        canonical = soup.find("link", rel=lambda value: value and "canonical" in value)
        canonical_url = urljoin(response.url, canonical.get("href", "")) if canonical else ""
        return PageTitle(
            url, response.url, response.status_code, canonical_url, title,
            h1.get_text(" ", strip=True) if h1 else "",
        )
    except requests.RequestException as error:
        return PageTitle(url, "", None, "", "", "", str(error))
 
def main() -> None:
    session = requests.Session()
    session.headers["User-Agent"] = USER_AGENT
    rows = []
    for url in load_sitemap(session):
        rows.append(inspect(session, url))
        time.sleep(DELAY_SECONDS)
 
    counts = Counter(normalized(row.title) for row in rows if row.title)
    with open(OUTPUT, "w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=[
            "requested_url", "final_url", "status", "canonical", "title",
            "title_characters", "duplicate_title", "h1", "error",
        ])
        writer.writeheader()
        for row in rows:
            writer.writerow({
                **row.__dict__,
                "title_characters": len(row.title),
                "duplicate_title": bool(row.title and counts[normalized(row.title)] > 1),
            })
 
if __name__ == "__main__":
    main()

The report preserves evidence before judgment

  • Redirect destination, status, canonical, title, H1, and fetch errors are captured separately.

  • Whitespace/case normalization finds near-identical literal titles without changing the displayed value.

  • A timeout prevents one broken page from hanging the whole run.

  • The script skips non-HTML responses instead of treating PDFs/images as missing titles.

  • It records character length only as a sorting signal; editorial decisions happen later.

Sitemap indexes need recursive discovery

  • Large sites often publish a <sitemapindex> pointing to several <sitemap> files rather than one <urlset>.

  • A production parser should detect the root element, fetch child sitemaps with depth/host/size limits, and deduplicate URLs.

  • Compressed .xml.gz sitemaps need bounded decompression and content validation.

  • Honor canonical host/protocol policy; unexpected external sitemap URLs should be rejected or explicitly allowlisted.

  • Compare sitemap URLs with internal links and CMS canonical records to expose coverage gaps.

3. Sort the review queue

SEO audit workspace with .venv activebash
python audit_titles.py
python - <<'PY'
import csv

with open("title-audit.csv", encoding="utf-8") as source:
    rows = list(csv.DictReader(source))

needs_review = [row for row in rows if (
    not row["title"]
    or row["duplicate_title"] == "True"
    or int(row["title_characters"] or 0) > 60
    or row["error"]
)]

for row in needs_review:
    print(row["title_characters"], row["requested_url"], row["title"], sep="\t")
PY
Rows with missing, duplicate, over-60-character, or fetch-error signals are printed for editorial review.

A threshold creates a queue, not a verdict

  • Review missing and duplicates before merely long titles.

  • Sort important URLs by impressions, clicks, CTR, conversions, revenue, or support value.

  • Segment page type so product, documentation, local, article, and pagination titles are judged by the correct intent.

  • Investigate fetch errors and redirect/canonical mismatches before editing metadata.

  • Do not bulk-trim live titles using character slicing; that can remove model numbers, error messages, versions, or differentiators.

Pixels, devices, and query context

  • Search result title links fit a device-dependent display area and may truncate as needed.

  • Wide letters consume more pixels than narrow letters, so equal character counts do not render equally.

  • Search engines may choose different title-link text for different queries or page signals.

  • A desktop preview does not guarantee mobile appearance.

  • Use pixel previews for editorial awareness, then validate actual result behavior and click data over time.

4. Rewrite around the page’s promise

  • Put the subject and differentiating task near the beginning.

  • Remove filler such as “Welcome to,” repeated category labels, and generic superlatives.

  • Keep version/platform/model terms when they materially qualify the answer.

  • Use one natural primary phrase; remove repeated synonyms written only for rankings.

  • Append concise brand text only when it adds recognition and does not overwhelm the unique title.

  • Match the page’s dominant language and writing system.

Before-and-after decisions

  • Before: “Complete Step by Step Guide to Learn How to Install Docker Container Software on Ubuntu Linux Operating System | Example.” After: “Install Docker Engine on Ubuntu | Example.”

  • Before: “Blue Running Shoes, Blue Jogging Shoes, Blue Athletic Shoes for Sale.” After: “Blue Running Shoes for Road and Track.”

  • Before: “Error 0x80070005.” After: “Fix Windows Error 0x80070005: Access Denied.”

  • Keep longer when needed: a precise API/error/version title may earn qualified clicks even if branding truncates; remove expendable suffixes before technical distinctions.

  • Do not add the current year automatically: dates belong only when freshness is part of the page promise and the content is actually maintained.

Fix title templates at their source

metadata.tstypescript
type PageMetadata = {
  title: string
  brand?: string
}
 
export function documentTitle({ title, brand = "Example" }: PageMetadata): string {
  const cleanTitle = title.replace(/\s+/g, " ").trim()
  const cleanBrand = brand.replace(/\s+/g, " ").trim()
 
  if (!cleanTitle) return cleanBrand
  if (!cleanBrand || cleanTitle.endsWith(`| ${cleanBrand}`)) return cleanTitle
  return `${cleanTitle} | ${cleanBrand}`
}

Template logic prevents repeated boilerplate bugs

  • Normalize accidental whitespace at the metadata boundary.

  • Return the brand only as a fallback; emit monitoring so empty page titles are fixed upstream.

  • Avoid appending an already-present brand suffix.

  • Do not truncate blindly in the template; content owners need visibility into titles that exceed the editorial review band.

  • Unit-test page types, empty values, Unicode, translations, pagination, filters, and brand variations.

Pagination, filters, and faceted URLs

  • Indexable paginated pages need distinct, descriptive signals consistent with canonical strategy.

  • Faceted/search/filter pages should not all inherit the same generic category title.

  • Decide which filter combinations deserve indexable canonical pages; do not use title edits to disguise duplicate/thin URL proliferation.

  • Align canonical tags, robots directives, internal links, sitemap inclusion, and titles.

  • Use parameter/facet rules carefully—incorrect noindex, canonical, or robots controls can remove valuable pages or waste crawl resources.

CMS fields must map to one clear ownership model

  • Define whether SEO title falls back to editorial title, H1, product name, or a page-type template.

  • Avoid two plugins/components emitting competing <title> elements.

  • Keep H1 and SEO title separately editable only when the editorial workflow benefits.

  • Enforce uniqueness warnings and sensible preview guidance, not a destructive hard limit.

  • Audit the rendered HTML because database fields do not prove what SSR, plugins, JavaScript, localization, or templates output.

5. Verify the rendered page

Any shellbash
curl -sSL https://example.com/page/ | python3 - <<'PY'
import sys
from bs4 import BeautifulSoup
html = sys.stdin.read()
soup = BeautifulSoup(html, "html.parser")
print("title:", soup.title.get_text(" ", strip=True) if soup.title else "MISSING")
print("h1:", [node.get_text(" ", strip=True) for node in soup.find_all("h1")])
print("canonical:", [node.get("href") for node in soup.find_all("link", rel="canonical")])
PY
The parser prints the final fetched document title, all H1 values, and canonical link targets.

Rendered evidence catches integration failures

  • -L follows redirects so the final page is inspected.

  • Check status/redirect history separately when a redirect was not expected.

  • SSR HTML is what non-JavaScript retrieval sees; JavaScript-rendered metadata needs rendered/browser inspection too.

  • Exactly one meaningful main title is usually clearest, though HTML does not technically forbid multiple H1 elements.

  • Validate canonical consistency and robots/indexability alongside title changes.

6. Measure after deployment

  • Recrawl a sample immediately and the full inventory on the next scheduled audit.

  • Use URL Inspection for a few important changed URLs; requesting repeatedly does not accelerate crawling.

  • Use sitemaps for broader discovery and keep lastmod truthful.

  • Track Google/Bing query-page impressions, clicks, CTR, average position, and title-link appearance over a long enough window to reduce noise.

  • Annotate deployment dates and account for seasonality, ranking changes, SERP features, and content updates.

  • Search engines can take days or weeks to recrawl/reprocess and may still choose alternate title-link text.

  • The source title is missing, half-empty, obsolete, inaccurate, repetitive, or stuffed.

  • Several pages share micro-boilerplate and omit the distinguishing attribute.

  • The visible main heading communicates the page better.

  • The page language/script differs from the title.

  • Brand/site naming is duplicated or overly prominent.

  • Anchor text and other prominent page signals suggest a more useful label.

Common audit mistakes

  • Flagging every title above 60 characters as an error: length is contextual triage.

  • Editing only high-traffic pages manually: fix the template/data source that keeps generating failures.

  • Crawling only the sitemap: add link/CMS/search/log inventories.

  • Looking only at CMS exports: validate final canonical HTML and rendered variants.

  • Removing all brand text: concise recognized branding can help users; repetitive boilerplate can hurt clarity.

  • Stuffing more keywords to avoid rewrites: improve accuracy and signal alignment instead.

  • Changing titles daily: allow recrawl and collect enough comparable data.

  • Assuming displayed search text is guaranteed: title links and truncation are automated and contextual.

Publication checklist

  • Every indexable canonical HTML page has one non-empty, unique, descriptive title.

  • The distinguishing subject/task/entity appears early enough to remain understandable when truncated.

  • Title, H1, visible content, language, social metadata, canonical, and structured data describe the same page.

  • Boilerplate, keyword repetition, obsolete dates, and inaccurate claims are removed.

  • Templates have tests/monitoring and do not silently truncate editorial fields.

  • Changes are recrawled, measured, and reviewed against real page-query performance.

Primary search documentation