Root Layout Metadata
export const metadata = {
title: {
default: 'My Website',
template: '%s | My Website'
},
description: 'A comprehensive guide to Next.js development',
keywords: ['Next.js', 'React', 'JavaScript', 'Web Development'],
authors: [{ name: 'John Doe' }],
creator: 'John Doe',
publisher: 'My Company',
formatDetection: {
email: false,
address: false,
telephone: false,
},
metadataBase: new URL('https://example.com'),
alternates: {
canonical: '/',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Page-Level Metadata
export const metadata = {
title: 'About Us',
description: 'Learn more about our company and mission',
keywords: ['about', 'company', 'mission'],
openGraph: {
title: 'About Us',
description: 'Learn more about our company and mission',
url: 'https://example.com/about',
siteName: 'My Website',
images: [
{
url: 'https://example.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'About Us',
},
],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'About Us',
description: 'Learn more about our company and mission',
images: ['https://example.com/twitter-image.jpg'],
},
};
export default function AboutPage() {
return <div>About page content</div>;
}
Generate Metadata Function
export async function generateMetadata({ params, searchParams }) {
const post = await getPost(params.slug);
if (!post) {
return {
title: 'Post Not Found',
};
}
return {
title: post.title,
description: post.excerpt,
keywords: post.tags,
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://example.com/blog/${params.slug}`,
images: [
{
url: post.featuredImage,
width: 1200,
height: 630,
alt: post.title,
},
],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
tags: post.tags,
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.featuredImage],
},
};
}
Metadata with Search Params
export async function generateMetadata({ searchParams }) {
const query = searchParams.q || '';
return {
title: query ? `Search Results for "${query}"` : 'Search',
description: query
? `Search results for "${query}" on our website`
: 'Search our website for articles, products, and more',
robots: {
index: false,
follow: true,
},
};
}
Basic Open Graph
export const metadata = {
openGraph: {
title: 'Page Title',
description: 'Page description',
url: 'https://example.com/page',
siteName: 'My Website',
images: [
{
url: 'https://example.com/image.jpg',
width: 1200,
height: 630,
alt: 'Image description',
},
],
locale: 'en_US',
type: 'website',
},
};
Article Open Graph
export const metadata = {
openGraph: {
type: 'article',
title: 'Article Title',
description: 'Article description',
url: 'https://example.com/article',
siteName: 'My Website',
images: [
{
url: 'https://example.com/article-image.jpg',
width: 1200,
height: 630,
alt: 'Article featured image',
},
],
publishedTime: '2024-01-15T10:00:00Z',
modifiedTime: '2024-01-16T15:30:00Z',
authors: ['John Doe', 'Jane Smith'],
tags: ['technology', 'programming', 'nextjs'],
section: 'Technology',
},
};
Product Open Graph
export const metadata = {
openGraph: {
type: 'product',
title: 'Product Name',
description: 'Product description',
url: 'https://example.com/product',
siteName: 'My Store',
images: [
{
url: 'https://example.com/product-image.jpg',
width: 1200,
height: 630,
alt: 'Product image',
},
],
price: {
amount: '99.99',
currency: 'USD',
},
availability: 'in stock',
brand: 'Brand Name',
category: 'Electronics',
},
};
Summary Card
export const metadata = {
twitter: {
card: 'summary',
title: 'Page Title',
description: 'Page description',
images: ['https://example.com/image.jpg'],
creator: '@username',
site: '@sitehandle',
},
};
Large Image Card
export const metadata = {
twitter: {
card: 'summary_large_image',
title: 'Page Title',
description: 'Page description',
images: ['https://example.com/large-image.jpg'],
creator: '@username',
site: '@sitehandle',
},
};
App Card
export const metadata = {
twitter: {
card: 'app',
title: 'App Name',
description: 'App description',
images: ['https://example.com/app-icon.jpg'],
app: {
name: {
iphone: 'App Name',
ipad: 'App Name',
googleplay: 'App Name',
},
id: {
iphone: '123456789',
ipad: '123456789',
googleplay: 'com.example.app',
},
url: {
iphone: 'https://apps.apple.com/app/id123456789',
ipad: 'https://apps.apple.com/app/id123456789',
googleplay: 'https://play.google.com/store/apps/details?id=com.example.app',
},
},
},
};
Article Structured Data
export default function BlogPost({ params }) {
const post = await getPost(params.slug);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.excerpt,
image: post.featuredImage,
author: {
'@type': 'Person',
name: post.author,
},
publisher: {
'@type': 'Organization',
name: 'My Website',
logo: {
'@type': 'ImageObject',
url: 'https://example.com/logo.png',
},
},
datePublished: post.publishedAt,
dateModified: post.updatedAt,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://example.com/blog/${params.slug}`,
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
</>
);
}
Organization Structured Data
export default function RootLayout({ children }) {
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'My Company',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
sameAs: [
'https://twitter.com/mycompany',
'https://linkedin.com/company/mycompany',
'https://facebook.com/mycompany',
],
contactPoint: {
'@type': 'ContactPoint',
telephone: '+1-555-123-4567',
contactType: 'customer service',
},
};
return (
<html lang="en">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
</head>
<body>{children}</body>
</html>
);
}
Canonical URLs
export const metadata = {
alternates: {
canonical: 'https://example.com/page',
languages: {
'en-US': 'https://example.com/en-US/page',
'es-ES': 'https://example.com/es-ES/page',
},
},
};
Robots Meta Tags
export const metadata = {
robots: {
index: true,
follow: true,
nocache: true,
googleBot: {
index: true,
follow: true,
noimageindex: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
Sitemap Generation
export default async function sitemap() {
const baseUrl = 'https://example.com';
const posts = await getAllPosts();
const blogUrls = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly',
priority: 0.7,
}));
const staticPages = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${baseUrl}/contact`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.5,
},
];
return [...staticPages, ...blogUrls];
}
Robots.txt
export default function robots() {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/private/', '/admin/', '/api/'],
},
],
sitemap: 'https://example.com/sitemap.xml',
};
}
Conditional Metadata
export async function generateMetadata({ params }) {
const product = await getProduct(params.id);
if (!product) {
return {
title: 'Product Not Found',
robots: {
index: false,
follow: false,
},
};
}
const metadata = {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
images: [product.image],
type: 'product',
},
};
if (product.price) {
metadata.openGraph.price = {
amount: product.price.toString(),
currency: 'USD',
};
}
if (product.inStock) {
metadata.openGraph.availability = 'in stock';
} else {
metadata.openGraph.availability = 'out of stock';
}
return metadata;
}
Metadata with API Data
export async function generateMetadata() {
const user = await getCurrentUser();
return {
title: user ? `${user.name}'s Dashboard` : 'Dashboard',
description: user
? `Welcome back, ${user.name}! Manage your account and preferences.`
: 'Access your personalized dashboard.',
robots: {
index: false,
follow: false,
},
};
}
Metadata for Different Environments
export function getBaseMetadata() {
const isProduction = process.env.NODE_ENV === 'production';
const baseUrl = isProduction
? 'https://example.com'
: 'http://localhost:3000';
return {
metadataBase: new URL(baseUrl),
robots: {
index: isProduction,
follow: isProduction,
},
};
}
import { getBaseMetadata } from '@/lib/metadata';
export const metadata = {
...getBaseMetadata(),
title: {
default: 'My Website',
template: '%s | My Website'
},
description: 'Website description',
};
Metadata Caching
const metadataCache = new Map();
export async function getCachedMetadata(key, fetcher) {
if (metadataCache.has(key)) {
return metadataCache.get(key);
}
const metadata = await fetcher();
metadataCache.set(key, metadata);
return metadata;
}
export async function generateMetadata({ params }) {
return getCachedMetadata(`post-${params.slug}`, async () => {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
});
}
Lazy Metadata Loading
export async function generateMetadata({ params }) {
const post = await getPostBasic(params.slug);
const metadata = {
title: post.title,
description: post.excerpt,
};
const [fullPost, author] = await Promise.all([
getPostFull(params.slug),
getAuthor(post.authorId),
]);
return {
...metadata,
openGraph: {
title: fullPost.title,
description: fullPost.excerpt,
images: [fullPost.featuredImage],
authors: [author.name],
},
};
}
Metadata Validation
export function validateMetadata(metadata) {
const errors = [];
if (!metadata.title) {
errors.push('Title is required');
}
if (!metadata.description) {
errors.push('Description is required');
}
if (metadata.description && metadata.description.length > 160) {
errors.push('Description should be under 160 characters');
}
if (metadata.openGraph?.images) {
metadata.openGraph.images.forEach((image, index) => {
if (!image.url) {
errors.push(`Open Graph image ${index} is missing URL`);
}
if (!image.alt) {
errors.push(`Open Graph image ${index} is missing alt text`);
}
});
}
return errors;
}
Metadata Testing
import { generateMetadata } from '../app/blog/[slug]/page';
describe('Blog Post Metadata', () => {
it('should generate correct metadata for valid post', async () => {
const metadata = await generateMetadata({
params: { slug: 'test-post' }
});
expect(metadata.title).toBeDefined();
expect(metadata.description).toBeDefined();
expect(metadata.openGraph).toBeDefined();
expect(metadata.openGraph.title).toBe(metadata.title);
});
it('should handle missing posts gracefully', async () => {
const metadata = await generateMetadata({
params: { slug: 'non-existent' }
});
expect(metadata.title).toBe('Post Not Found');
expect(metadata.robots.index).toBe(false);
});
});