SEO and metadata

Maintain unique title, meta description, and canonical per indexable route; pair Open Graph and Twitter fields; align with hreflang, structured data, and robots/sitemap policy.

For SPA + SSR mixes, the SKILL should state whether first HTML carries critical metadata; CSR-only routes may need prerender or a dynamic meta API.

JSON-LD must reflect visible content—no keyword stuffing. Multilingual sites configure hreflang and x-default with canonical URLs that match production host policy.

Metadata pipeline (skill-flow)

  [ Route / build determines page identity ]
              │
              ▼
   ┌────────────────────│
   │HTML head:         │──── title, meta description, canonical
   │uniqueness + URL   │     (matches user-visible URL)
   └────────────────────│
              │
              ▼
   ┌────────────────────│
   │Social layer:      │──── og:title / og:description / og:url
   │OG + Twitter       │     twitter:* mirrors or overrides OG
   └────────────────────│     og:image / twitter:image: absolute HTTPS
              │
              ▼
   ┌────────────────────│
   │Extensions:        │──── JSON-LD (visible content only)
   │hreflang / schema  │     multilingual alternates + x-default
   └────────────────────│
              │
              ▼
   ┌────────────────────│
   │Crawl policy:      │──── robots.txt, sitemap, noindex
   │staging / accounts │     validate in Search Console–class tools
   └────────────────────┘

Production should expose absolute HTTPS URLs for og:image and twitter:image (often 1200×630) with og:image:alt; this site’s head intentionally omits fixed image URLs to avoid broken assets.

Uniqueness and canonical

  • Title and description lengths fit SERP norms; avoid sitewide duplicates.
  • Canonicalize query, pagination, and tracking variants to the primary URL.
  • Keep Core Web Vitals separate from SEO concerns inside the SKILL.

Complete HTML head example with required meta tags (SEO + Open Graph + Twitter Card):

<!-- Complete HTML head example: SEO + Open Graph + Twitter Card -->
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Core SEO: title 55-65 chars, description 150-160 chars -->
  <title>Product Name - Concise Value Proposition | Brand</title>
  <meta name="description"
    content="Under 160 characters summarizing the page with core keywords, written to encourage clicks. Unique per page." />

  <!-- canonical: eliminate duplicate indexing (tracking params, pagination, etc.) -->
  <link rel="canonical" href="https://www.example.com/products/widget" />

  <!-- Open Graph (social sharing) -->
  <meta property="og:type"        content="article" />
  <meta property="og:url"         content="https://www.example.com/products/widget" />
  <meta property="og:title"       content="Product Name - Value Proposition" />
  <meta property="og:description" content="Summary for social sharing; may differ from meta description." />
  <meta property="og:image"       content="https://www.example.com/og/widget.jpg" />
  <meta property="og:image:alt"   content="Text description of product image (accessibility)" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
  <meta property="og:locale"      content="en_US" />
  <meta property="og:site_name"   content="Brand Name" />

  <!-- Twitter Card -->
  <meta name="twitter:card"        content="summary_large_image" />
  <meta name="twitter:title"       content="Product Name (can be shorter than og:title)" />
  <meta name="twitter:description" content="Twitter share summary." />
  <meta name="twitter:image"       content="https://www.example.com/og/widget.jpg" />
  <meta name="twitter:image:alt"   content="Product image description" />
  <meta name="twitter:site"        content="@yourbrand" />

  <!-- Multilingual hreflang -->
  <link rel="alternate" hreflang="en"
        href="https://www.example.com/products/widget" />
  <link rel="alternate" hreflang="zh-CN"
        href="https://www.example.com/zh/products/widget" />
  <link rel="alternate" hreflang="x-default"
        href="https://www.example.com/products/widget" />
</head>

Open Graph and Twitter

og:url should match canonical; with twitter:card=summary_large_image, provide large image URLs and meaningful alt.

  • Maintain share image dimensions and alt separately from the page hero if needed.
  • Twitter-specific tags may override OG when you want shorter share titles.

Structured data and hreflang

JSON-LD types must match the page (Article, Product, BreadcrumbList, etc.); do not emit schema for invisible content.

JSON-LD structured data examples (Article / Product / BreadcrumbList):

<!-- Article JSON-LD -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Article Title (under 110 characters)",
  "description": "Article summary, may match meta description.",
  "author": {
    "@type": "Person",
    "name": "Author Name",
    "url": "https://www.example.com/authors/jane"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Publisher Name",
    "logo": { "@type": "ImageObject", "url": "https://www.example.com/logo.png" }
  },
  "datePublished": "2024-01-15T08:00:00Z",
  "dateModified": "2024-03-20T12:00:00Z",
  "image": "https://www.example.com/articles/hero.jpg",
  "url": "https://www.example.com/articles/my-article"
}
</script>

<!-- Product JSON-LD -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Product Name",
  "image": "https://www.example.com/products/widget.jpg",
  "description": "Product description.",
  "sku": "WIDGET-001",
  "offers": {
    "@type": "Offer",
    "price": "29.99",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock",
    "url": "https://www.example.com/products/widget"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.8",
    "reviewCount": "120"
  }
}
</script>

<!-- BreadcrumbList JSON-LD -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://www.example.com/" },
    { "@type": "ListItem", "position": 2, "name": "Products", "item": "https://www.example.com/products/" },
    { "@type": "ListItem", "position": 3, "name": "Widget", "item": "https://www.example.com/products/widget" }
  ]
}
</script>

robots, sitemap, and noindex

Split responsibilities: robots.txt, sitemap, and noindex for staging, account pages, and duplicate parameter URLs—re-validate after changes.

robots.txt configuration example:

# /robots.txt — production
User-agent: *
Allow: /

# Block indexing of account pages, duplicate params, and API paths
Disallow: /account/
Disallow: /api/
Disallow: /*?ref=     # tracking parameters
Disallow: /*?sort=    # sort-only parameter pages
Disallow: /search?q=  # empty search result pages

# Declare sitemap location
Sitemap: https://www.example.com/sitemap.xml
Sitemap: https://www.example.com/sitemap-products.xml

sitemap.xml generation script (Node.js, compatible with Next.js / static sites):

// scripts/generate-sitemap.mjs
import { writeFileSync } from 'fs';
import { getAllProducts, getAllArticles } from './lib/api.mjs';

const BASE_URL = 'https://www.example.com';

async function generateSitemap() {
  const products = await getAllProducts();
  const articles = await getAllArticles();

  const staticPages = ['/', '/about', '/contact', '/products'];

  const urls = [
    // Static pages
    ...staticPages.map(path => ({
      loc: `${BASE_URL}${path}`,
      changefreq: 'weekly',
      priority: path === '/' ? '1.0' : '0.8',
      lastmod: new Date().toISOString().split('T')[0],
    })),
    // Dynamic product pages
    ...products.map(p => ({
      loc: `${BASE_URL}/products/${p.slug}`,
      changefreq: 'daily',
      priority: '0.9',
      lastmod: p.updatedAt,
    })),
    // Article pages
    ...articles.map(a => ({
      loc: `${BASE_URL}/articles/${a.slug}`,
      changefreq: 'monthly',
      priority: '0.7',
      lastmod: a.updatedAt,
    })),
  ];

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(u => `  <url>
    <loc>${u.loc}</loc>
    <lastmod>${u.lastmod}</lastmod>
    <changefreq>${u.changefreq}</changefreq>
    <priority>${u.priority}</priority>
  </url>`).join('\n')}
</urlset>`;

  writeFileSync('./public/sitemap.xml', xml);
  console.log(`Generated sitemap with ${urls.length} URLs`);
}

generateSitemap();

SKILL snippet

---
name: seo-metadata
description: Maintain page SEO metadata, canonical URLs, and structured data
tags: [seo, metadata, structured-data, sitemap]
---
# Basic Metadata
- title: 55-65 chars, unique per page; format: keyword - value proposition | brand
- meta description: 150-160 chars with core keywords, never duplicated site-wide
- canonical matches the final user-visible URL, eliminating duplicate indexing from params/pagination

# Open Graph and Twitter Card
- og:url must exactly match canonical
- og:image: absolute HTTPS URL, 1200x630, with og:image:alt
- twitter:card = summary_large_image requires twitter:image
- og:locale corresponds to page language (en_US / zh_CN)

# Structured Data (JSON-LD)
- Type matches visible page content: Article / Product / BreadcrumbList
- Never generate JSON-LD for invisible or synthetic content (avoid penalties)
- Validate with Google Rich Results Test after each deployment

# Multilingual and Crawl Policy
- hreflang comes in pairs (mutually referencing); x-default points to the default locale
- robots.txt blocks account pages, API paths, tracking parameter variants
- Staging environment uses noindex in HTTP header or meta tag
- sitemap.xml contains only indexable canonical URLs; lastmod uses real modification time

# Core Web Vitals and CI
- LCP < 2.5s: prioritize image sizing, preload, CDN
- CLS < 0.1: set explicit width/height on images/ads/fonts to prevent layout shift
- INP < 200ms: reduce long tasks, split interaction handlers into idle frames
- Lighthouse CI gates performance / SEO / accessibility scores in PRs

Head checklist (page JS)

Generate an agent-facing head checklist from render mode and toggles—aligned with the pipeline above.

Render mode
Checks

Output


                

Back to skills More skills