Good SEO starts with proper metadata. Next.js provides the Metadata API for type-safe, automatic meta tag generation. Export a metadata object from your pages and layouts, and Next.js generates the HTML <head> tags automatically. With metadata inheritance, you set defaults once and customize per page. Let's master SEO optimization in Next.js!
Why Metadata Matters for SEO
What Search Engines Use
- Title: Appears in search results and browser tabs
- Description: Shown in search result snippets
- Keywords: Help categorize content (less important now)
- Open Graph: Controls social media previews
- Canonical URL: Prevents duplicate content penalties
- Robots: Controls crawling and indexing
SEO Impact
Good metadata improves click-through rates from search results, prevents duplicate content issues, controls how pages appear on social media, and helps search engines understand your content. Proper metadata is essential for visibility.
Basic Metadata Configuration
Simple Page Metadata
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn about our company, mission, and team.',
};
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Welcome to our about page!</p>
</div>
);
}
// Generates:
// <title>About Us</title>
// <meta name="description" content="Learn about our company..." />
// ✅ Type-safe with TypeScript
// ✅ Automatic meta tag generation
// ✅ No manual <head> manipulation neededComprehensive Metadata Example
import { Metadata } from 'next';
export const metadata: Metadata = {
// Basic metadata
title: 'Home - My Awesome Site',
description: 'Welcome to My Awesome Site. We build amazing things.',
// Keywords (less important for modern SEO)
keywords: ['next.js', 'react', 'web development', 'tutorial'],
// Authors
authors: [
{ name: 'John Doe', url: 'https://johndoe.com' },
{ name: 'Jane Smith' },
],
// Creator
creator: 'My Awesome Company',
publisher: 'My Awesome Company',
// Application name
applicationName: 'My Awesome Site',
// Generator
generator: 'Next.js',
// Referrer policy
referrer: 'origin-when-cross-origin',
// Color scheme
colorScheme: 'light',
themeColor: '#ffffff',
// Viewport (default is already optimal)
// viewport: 'width=device-width, initial-scale=1',
};
export default function HomePage() {
return <div>Home Page</div>;
}
// ✅ Comprehensive SEO metadata
// ✅ All fields type-safe
// ✅ Automatic HTML generationMetadata Inheritance
Root Layout - Default Metadata
import { Metadata } from 'next';
export const metadata: Metadata = {
// Default title for all pages
title: {
template: '%s | My Awesome Site',
default: 'My Awesome Site',
},
// Default description
description: 'The best site on the internet',
// Application metadata
applicationName: 'My Awesome Site',
authors: [{ name: 'My Company' }],
// Default Open Graph
openGraph: {
siteName: 'My Awesome Site',
locale: 'en_US',
type: 'website',
},
// Robots
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>
);
}
// ✅ Default metadata for entire site
// ✅ title.template applies to all children
// ✅ title.default used when no child title setChild Page - Inherits and Overrides
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn about our company and team.',
};
export default function AboutPage() {
return <div>About Us</div>;
}
// Final title: "About Us | My Awesome Site"
// ✅ Inherits template from root layout
// ✅ title becomes "About Us | My Awesome Site"
// ✅ Overrides description
// ✅ Keeps other metadata from rootHow Inheritance Works
Metadata Cascade
// Root layout:
{
title: { template: '%s | Site', default: 'Site' },
description: 'Default description',
keywords: ['default'],
}
// Child page:
{
title: 'About',
description: 'About page',
}
// Final result:
{
title: 'About | Site', // Template applied
description: 'About page', // Overridden
keywords: ['default'], // Inherited
}
// ✅ Child overrides parent
// ✅ Unspecified fields inherited
// ✅ Template applies to child titlesAdvanced Title Configuration
Title Templates
// Template with %s placeholder
export const metadata = {
title: {
template: '%s | My Site',
default: 'My Site',
},
};
// Child pages:
// title: 'About' → "About | My Site"
// title: 'Blog' → "Blog | My Site"
// title: 'Contact' → "Contact | My Site"
// If child doesn't set title → "My Site" (default)
// ✅ Consistent branding
// ✅ DRY - set suffix once
// ✅ Automatic for all childrenAbsolute Title (Skip Template)
// Use absolute title to skip template
export const metadata = {
title: {
absolute: 'Special Page - No Template Applied',
},
};
// Result: "Special Page - No Template Applied"
// ✅ Template NOT applied
// ✅ Use for pages that need exact title
// ✅ Good for landing pages, campaignsSection-Specific Templates
import { Metadata } from 'next';
export const metadata: Metadata = {
title: {
template: '%s - Blog | My Site',
default: 'Blog | My Site',
},
};
export default function BlogLayout({ children }) {
return <div>{children}</div>;
}
// Blog post titles:
// title: 'My First Post' → "My First Post - Blog | My Site"
// title: 'React Tips' → "React Tips - Blog | My Site"
// ✅ Blog-specific template
// ✅ Overrides root template
// ✅ Consistent blog brandingCommon Meta Tags
Open Graph Basics
export const metadata: Metadata = {
title: 'My Page',
description: 'Page description',
openGraph: {
title: 'My Page', // Can differ from page title
description: 'Page description for social media',
url: 'https://example.com/page',
siteName: 'My Site',
locale: 'en_US',
type: 'website',
images: [
{
url: 'https://example.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'My Page Image',
},
],
},
};
// Generates:
// <meta property="og:title" content="My Page" />
// <meta property="og:description" content="..." />
// <meta property="og:image" content="..." />
// <meta property="og:url" content="..." />
// ✅ Controls social media previews
// ✅ Facebook, LinkedIn, etc.
// ✅ Type-safe configurationTwitter Cards
export const metadata: Metadata = {
twitter: {
card: 'summary_large_image',
title: 'My Page',
description: 'Page description for Twitter',
creator: '@myhandle',
site: '@mysite',
images: ['https://example.com/twitter-image.jpg'],
},
};
// Card types:
// - 'summary': Small image
// - 'summary_large_image': Large image (recommended)
// - 'app': App card
// - 'player': Video/audio player
// ✅ Twitter-specific previews
// ✅ Different from Open Graph if needed
// ✅ Large image for better engagementCanonical URLs and Alternates
export const metadata: Metadata = {
// Canonical URL (prevents duplicate content)
alternates: {
canonical: 'https://example.com/preferred-url',
// Alternate languages
languages: {
'en-US': 'https://example.com/en-us',
'de-DE': 'https://example.com/de-de',
'fr-FR': 'https://example.com/fr-fr',
},
},
};
// Generates:
// <link rel="canonical" href="https://example.com/preferred-url" />
// <link rel="alternate" hreflang="en-US" href="..." />
// <link rel="alternate" hreflang="de-DE" href="..." />
// ✅ Prevents duplicate content penalties
// ✅ Indicates preferred URL
// ✅ Multi-language supportRobots and Indexing
export const metadata: Metadata = {
robots: {
index: true, // Allow indexing
follow: true, // Follow links
nocache: false, // Allow caching
// Google-specific
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
// Generates:
// <meta name="robots" content="index, follow" />
// <meta name="googlebot" content="index, follow, max-video-preview:-1..." />
// ✅ Control search engine behavior
// ✅ Prevent indexing of sensitive pages
// ✅ Google-specific optimizations
// Example: Don't index admin pages
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
},
};Verification Tags
export const metadata: Metadata = {
verification: {
google: 'google-verification-code',
yandex: 'yandex-verification-code',
yahoo: 'yahoo-verification-code',
other: {
'fb:app_id': 'facebook-app-id',
},
},
};
// Generates:
// <meta name="google-site-verification" content="..." />
// <meta name="yandex-verification" content="..." />
// ✅ Verify site ownership
// ✅ Enable search console access
// ✅ Social media verificationIcons and Manifests
Favicon and Icons
export const metadata: Metadata = {
icons: {
icon: '/favicon.ico',
shortcut: '/shortcut-icon.png',
apple: '/apple-icon.png',
other: [
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
url: '/icon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
url: '/icon-16x16.png',
},
],
},
};
// Generates:
// <link rel="icon" href="/favicon.ico" />
// <link rel="shortcut icon" href="/shortcut-icon.png" />
// <link rel="apple-touch-icon" href="/apple-icon.png" />
// <link rel="icon" type="image/png" sizes="32x32" href="..." />
// ✅ Multiple icon sizes
// ✅ Apple touch icons
// ✅ Favicon configurationWeb App Manifest
export const metadata: Metadata = {
manifest: '/manifest.json',
// App-specific metadata
applicationName: 'My Awesome App',
appleWebApp: {
capable: true,
statusBarStyle: 'black-translucent',
title: 'My Awesome App',
},
};
// manifest.json:
{
"name": "My Awesome App",
"short_name": "MyApp",
"description": "An awesome progressive web app",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
// ✅ PWA support
// ✅ App-like experience
// ✅ Install to home screenComplete Metadata Examples
Example 1: E-commerce Product Metadata
import { Metadata } from 'next';
export const metadata: Metadata = {
title: {
template: '%s - Products | My Store',
default: 'Products | My Store',
},
description: 'Browse our amazing products',
openGraph: {
type: 'website',
siteName: 'My Store',
},
};
export default function ProductsLayout({ children }) {
return <div>{children}</div>;
}import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Amazing Product',
description: 'The best product you will ever buy',
keywords: [
'product',
'e-commerce',
'buy online',
'amazing',
],
openGraph: {
title: 'Amazing Product',
description: 'The best product you will ever buy',
type: 'product',
images: [
{
url: 'https://mystore.com/products/amazing-product.jpg',
width: 1200,
height: 630,
alt: 'Amazing Product',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Amazing Product',
description: 'The best product you will ever buy',
images: ['https://mystore.com/products/amazing-product.jpg'],
},
alternates: {
canonical: 'https://mystore.com/products/amazing-product',
},
};
export default function ProductPage() {
return <div>Product Details</div>;
}
// ✅ Product-specific metadata
// ✅ Open Graph type: 'product'
// ✅ Rich social previews
// ✅ Canonical URLExample 2: Blog Article Metadata
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Understanding React Server Components',
description: 'A deep dive into React Server Components and how they work in Next.js',
authors: [{ name: 'John Doe', url: 'https://johndoe.com' }],
keywords: [
'react',
'server components',
'next.js',
'web development',
],
openGraph: {
title: 'Understanding React Server Components',
description: 'A deep dive into React Server Components',
type: 'article',
publishedTime: '2024-01-15T00:00:00.000Z',
authors: ['John Doe'],
tags: ['React', 'Next.js', 'Server Components'],
images: [
{
url: 'https://myblog.com/articles/rsc-cover.jpg',
width: 1200,
height: 630,
alt: 'React Server Components',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Understanding React Server Components',
description: 'A deep dive into React Server Components',
creator: '@johndoe',
images: ['https://myblog.com/articles/rsc-cover.jpg'],
},
alternates: {
canonical: 'https://myblog.com/blog/react-server-components',
},
robots: {
index: true,
follow: true,
},
};
export default function BlogPostPage() {
return <article>Blog content...</article>;
}
// ✅ Article-specific metadata
// ✅ Open Graph type: 'article'
// ✅ Published time and authors
// ✅ Rich Twitter cardsMetadata Configuration Structure
Organization of metadata across pages and layouts
Select a file or folder to see details
Metadata Best Practices
1. Set Defaults in Root Layout
// ✅ GOOD: Defaults in root layout
// app/layout.tsx
export const metadata = {
title: {
template: '%s | My Site',
default: 'My Site',
},
description: 'Default description',
openGraph: {
siteName: 'My Site',
},
};
// Pages only override what's different
// ❌ BAD: Repeating everything on every page2. Use Descriptive Titles and Descriptions
// ✅ GOOD: Descriptive and specific
export const metadata = {
title: 'Next.js Server Actions Tutorial - Complete Guide',
description: 'Learn how to use Server Actions in Next.js 15 for form handling, mutations, and data updates with step-by-step examples.',
};
// ❌ BAD: Generic and vague
export const metadata = {
title: 'Tutorial',
description: 'Learn stuff',
};
// Good titles: 50-60 characters
// Good descriptions: 150-160 characters3. Include Keywords Naturally
// ✅ GOOD: Natural keyword inclusion
export const metadata = {
title: 'React Server Components Tutorial',
description: 'Learn React Server Components in Next.js 15 with practical examples and best practices for building fast web applications.',
keywords: ['react', 'server components', 'next.js', 'tutorial'],
};
// ❌ BAD: Keyword stuffing
export const metadata = {
description: 'react server components react tutorial react next.js react components...',
};
// Keywords matter less than content quality4. Always Set Canonical URLs
// ✅ GOOD: Canonical prevents duplicate content
export const metadata = {
alternates: {
canonical: 'https://example.com/preferred-url',
},
};
// Especially important for:
// - Pages with query parameters
// - Multiple URLs for same content
// - Syndicated content5. Optimize for Social Sharing
// ✅ GOOD: Rich social previews
export const metadata = {
title: 'My Awesome Article',
description: 'Great description',
openGraph: {
title: 'My Awesome Article',
description: 'Great description',
images: [
{
url: 'https://example.com/og-image.jpg',
width: 1200,
height: 630, // Recommended Open Graph size
alt: 'Descriptive alt text',
},
],
},
twitter: {
card: 'summary_large_image',
images: ['https://example.com/twitter-image.jpg'],
},
};
// Large images get more engagement
// Alt text improves accessibilityKey Takeaways
- Export metadata object - type-safe configuration
- Metadata inheritance - children merge with parents
- Title templates - consistent branding with %s
- Open Graph - controls social media previews
- Twitter cards - Twitter-specific metadata
- Canonical URLs - prevents duplicate content
- Robots meta - controls indexing
- Server Components only - metadata export not in Client Components
What's Next?
You've mastered static metadata! Next, we'll explore Dynamic Metadata Generation—generating metadata based on page data, route parameters, and external sources. You'll learn generateMetadata function, async metadata, and creating dynamic titles and descriptions for database-driven content!
We'll cover generateMetadata, fetching data for metadata, type-safe dynamic metadata, and optimizing metadata generation for performance.
🔍 SEO is Ongoing
Good metadata is the foundation of SEO, but it's just the start. Focus on quality content, fast loading times, mobile responsiveness, and good user experience. Metadata helps search engines understand your content—great content keeps users engaged!