From harness-claude
Defines static and dynamic SEO metadata, Open Graph tags, Twitter Cards, canonical URLs, robots.txt, and OG images using Next.js Metadata API in App Router.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Define SEO metadata, Open Graph tags, and dynamic OG images with the Metadata API
Optimizes Next.js App Router SEO with root metadata in layout.tsx, dynamic sitemaps, robots.txt, OpenGraph/Twitter cards, JSON-LD, and quick audit checklists for indexing issues.
Configures Nuxt SEO metadata: page titles, Open Graph tags, canonical URLs, robots directives, and structured data using useSeoMeta and useHead. For per-page customization and social previews.
Analyzes Next.js metadata for SEO: titles, descriptions, Open Graph, Twitter Cards. Detects App/Pages Router and language (CN/EN) for length, keyword, and best practice suggestions.
Share bugs, ideas, or general feedback.
Define SEO metadata, Open Graph tags, and dynamic OG images with the Metadata API
metadata constant from any layout.tsx or page.tsx for static metadata.generateMetadata({ params, searchParams }) function for dynamic metadata derived from route parameters or fetched data.title.template property in root layout.tsx to set a consistent title suffix — child pages set only the page-specific prefix.metadataBase in root metadata to resolve relative URLs in openGraph.images and twitter.images.opengraph-image.tsx in any route segment to generate a dynamic OG image with @vercel/og — no external service needed.robots, canonical, and alternates fields to control crawler behavior and avoid duplicate content.layout.tsx and page.tsx for the same route — define at the most specific level.// app/layout.tsx — root metadata with template and base URL
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: { default: 'My App', template: '%s | My App' },
description: 'The best app in the world',
openGraph: { type: 'website', locale: 'en_US', siteName: 'My App' },
};
// app/posts/[slug]/page.tsx — dynamic metadata
import type { Metadata } from 'next';
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const post = await fetchPost(params.slug);
return {
title: post.title, // becomes "Post Title | My App" via template
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: `/api/og?title=${encodeURIComponent(post.title)}`, width: 1200, height: 630 }],
},
};
}
// app/posts/[slug]/opengraph-image.tsx — dynamic OG image
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { slug: string } }) {
const post = await fetchPost(params.slug);
return new ImageResponse(<div style={{ fontSize: 48 }}>{post.title}</div>);
}
The Metadata API replaces <Head> from next/head in the App Router. It is declarative, type-safe, and tree-mergeable — parent layout metadata flows down to child pages, with child values winning on conflict.
title.template: The root layout sets title: { default: 'My App', template: '%s | My App' }. Every child page that sets title: 'Dashboard' automatically becomes 'Dashboard | My App'. Pages that do not set a title fall back to default.
metadataBase: Required when any metadata field uses a relative URL (especially openGraph.images). Without it, Next.js warns and relative URLs are not resolved correctly for social crawlers.
Dynamic OG images with opengraph-image.tsx: Place this file in any route segment directory. It exports an ImageResponse and runs on the Edge Runtime. Next.js automatically serves it at /{route}/opengraph-image and links it from the page's <meta> tags. No additional configuration needed.
Deduplication: Next.js merges metadata from all parent layouts. If a parent sets openGraph.images and a child does not, the parent's images are used. If the child sets openGraph.images, it replaces the parent's entirely — not merged.
Sitemap and robots: Create app/sitemap.ts (returns MetadataRoute.Sitemap) and app/robots.ts (returns MetadataRoute.Robots) for programmatic control of these files.
https://nextjs.org/docs/app/building-your-application/optimizing/metadata