pseo-metadata
pSEO Dynamic Metadata
Implement a metadata system that generates unique, accurate SEO tags for every programmatic page.
Core Principles
- Every page gets unique metadata: No two pages share the same title or description
- Data-driven generation: Metadata is constructed from structured content fields, not hardcoded
- Canonical by default: Every page declares its canonical URL
- Social-ready: Open Graph and Twitter cards are complete on every page
- Framework-native: Use the framework's built-in metadata API
Implementation Steps
1. Create the Metadata Generator
Build a centralized function that constructs metadata from page data:
Next.js App Router pattern:
// lib/metadata.ts
import type { Metadata } from "next";
export function generatePageMetadata(data: PageData, baseUrl: string): Metadata {
const title = buildTitle(data);
const description = buildDescription(data);
const canonicalUrl = `${baseUrl}${data.canonicalPath}`;
return {
title,
description,
alternates: { canonical: canonicalUrl },
openGraph: {
title,
description,
url: canonicalUrl,
type: "article",
siteName: "Site Name",
// images: [{ url, width, height, alt }],
},
twitter: {
card: "summary_large_image",
title,
description,
// images: [url],
},
};
}
2. Build Title Construction Logic
Titles must be:
- Unique across all pages (validate with pseo-quality-guard)
- 50-60 characters target length (hard cap at 70)
- Front-loaded with the primary keyword
- Multi-dimensional: Use 2+ data fields to construct, not just one variable
function buildTitle(data: PageData): string {
// Combine multiple data dimensions
// e.g., "{primary keyword} - {differentiator} | {brand}"
// NOT just "{keyword} | Brand" for every page
}
3. Build Description Construction Logic
Descriptions must be:
- Unique across all pages
- 150-160 characters target length
- Action-oriented: Include a value proposition or call to action
- Keyword-inclusive: Naturally incorporate the page's target keyword
4. Implement Canonical URLs
- Every page must set a self-referencing canonical URL
- Use absolute URLs (include protocol and domain)
- Canonicals must match the actual URL path exactly (no trailing slash mismatches)
- For paginated content, each page gets its own canonical
- If a page has query parameters, the canonical should point to the clean URL
5. Configure Open Graph Tags
Required OG tags for every pSEO page:
og:title- Can differ from the HTML title (no brand suffix needed)og:description- Can differ from meta descriptionog:url- Must match canonicalog:type- Usually "article" for content pages, "website" for hubsog:site_name- Consistent across all pagesog:image- Page-specific or category-specific image with alt textog:locale- Language/locale setting
6. Configure Twitter Card Tags
Required Twitter tags:
twitter:card- Usually "summary_large_image"twitter:title- Can match OG titletwitter:description- Can match OG descriptiontwitter:image- Can match OG image
7. Add Supplementary Meta Tags
Where appropriate:
robots: Default toindex, follow; setnoindexfor thin/utility pagesauthor: If content has an authorarticle:published_time/article:modified_time: For article-type pages
8. Export Metadata in Page Files
Wire the metadata generator into every page route:
// app/[category]/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const data = await getPageData(params.slug);
if (!data) return {};
return generatePageMetadata(data, process.env.NEXT_PUBLIC_BASE_URL);
}
9. Internationalization (i18n) Metadata
For multi-language pSEO sites, add language-specific metadata:
- hreflang tags: Declare all language variants of each page. Every page must link to all its translations including itself.
alternates: { canonical: canonicalUrl, languages: { "en": `${baseUrl}/en/${data.canonicalPath}`, "de": `${baseUrl}/de/${data.canonicalPath}`, "x-default": `${baseUrl}/en/${data.canonicalPath}`, }, } - x-default: Always include an
x-defaulthreflang pointing to the primary language or a language selector page - Canonical per language: Each language version gets its own canonical (e.g.,
/en/service/slugand/de/service/slugare each self-canonical) - og:locale: Set to the page's language (e.g.,
en_US,de_DE) - og:locale:alternate: List all other available locales
- URL structure: Use path prefixes (
/en/,/de/), not query params or subdomains, for best crawlability - Title and description: Must be translated, not just the body. Templated metadata in the target language.
Metadata Validation Rules
Run these checks (or integrate with pseo-quality-guard):
- No two pages produce the same title
- No two pages produce the same description
- All titles are between 30-70 characters
- All descriptions are between 100-170 characters
- All canonical URLs resolve to 200 status
- All OG tags are present on every page
- No page is missing metadata entirely
File Organization
lib/
metadata.ts # generatePageMetadata() and helpers
metadata.test.ts # validation tests for metadata generation
Relationship to Other Skills
- Depends on: pseo-data (content models provide metadata fields)
- Works with: pseo-templates (metadata is exported alongside page components)
- Validated by: pseo-quality-guard (checks uniqueness and completeness)
More from lisbeth718/pseo-skills
pseo-linking
Build an intelligent internal linking system using hub-and-spoke structures, related page suggestions, breadcrumb navigation, and topical clustering for programmatic SEO. Use when implementing or improving internal linking, adding breadcrumbs, building category hub pages, or creating related-pages components.
12pseo-scale
Architect programmatic SEO systems for 10K-100K+ pages with database-backed data layers, data sufficiency gating, incremental validation, crawl budget management, content enrichment pipelines, and edge delivery. Use when scaling beyond 10K pages, when builds are OOMing, when Google is not indexing all pages, or when the current in-memory architecture has hit its limits.
11