typescriptintermediate
SEO Metadata Patterns
Generate dynamic SEO metadata with Open Graph, Twitter cards, JSON-LD, and canonical URLs.
typescriptPress ⌘/Ctrl + Shift + C to copy
import type { Metadata, ResolvingMetadata } from 'next';
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com';
// Metadata factory for dynamic pages
interface PageSeoProps {
title: string;
description: string;
path: string;
image?: string;
type?: 'website' | 'article';
publishedTime?: string;
tags?: string[];
}
function generatePageMetadata(props: PageSeoProps): Metadata {
const { title, description, path, image, type = 'website', publishedTime, tags } = props;
const url = `${BASE_URL}${path}`;
const ogImage = image ?? `${BASE_URL}/og-default.png`;
return {
title,
description,
alternates: { canonical: url },
openGraph: {
title,
description,
url,
siteName: 'SnippetsLab',
type,
images: [{ url: ogImage, width: 1200, height: 630, alt: title }],
...(publishedTime && { publishedTime }),
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [ogImage],
},
other: tags ? { keywords: tags.join(', ') } : undefined,
};
}
// JSON-LD structured data component
function JsonLd({ data }: { data: Record<string, unknown> }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
// Article JSON-LD factory
function articleJsonLd(props: {
title: string;
description: string;
url: string;
datePublished: string;
tags?: string[];
}) {
return {
'@context': 'https://schema.org',
'@type': 'TechArticle',
headline: props.title,
description: props.description,
url: props.url,
datePublished: props.datePublished,
publisher: {
'@type': 'Organization',
name: 'SnippetsLab',
url: BASE_URL,
},
...(props.tags && { keywords: props.tags.join(', ') }),
};
}
// Usage in a page
export async function generateMetadata(
{ params }: { params: { slug: string } },
parent: ResolvingMetadata,
): Promise<Metadata> {
const snippet = await getSnippet(params.slug); // your data fetcher
return generatePageMetadata({
title: `${snippet.title} — SnippetsLab`,
description: snippet.description,
path: `/snippets/${params.slug}`,
type: 'article',
tags: snippet.tags,
});
}
async function getSnippet(slug: string) {
return { title: 'Example', description: 'A snippet', tags: ['react'] };
}
export { generatePageMetadata, JsonLd, articleJsonLd };Use Cases
- Dynamic page SEO
- Open Graph optimization
- Google rich results with JSON-LD
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptbeginner
Reusable Metadata Factory
Factory function to generate consistent SEO metadata across all pages with Open Graph and canonical URLs.
Best for: Consistent page SEO
#seo#metadata
typescriptintermediate
Dynamic Metadata with generateMetadata
Generate dynamic SEO metadata based on page params using generateMetadata in Next.js App Router.
Best for: blog SEO
#nextjs#metadata
typescriptadvanced
Dynamic OG Image Generation
Generate Open Graph images on-the-fly using Next.js ImageResponse with custom fonts and dynamic content.
Best for: Social media sharing
#og-image#seo
typescriptadvanced
Next.js Dynamic OG Image Generation
Generate dynamic Open Graph images at build time using next/og for social media sharing.
Best for: Dynamic social media preview images
#nextjs#og-image