Next.js Metadata and SEO
Answer
Next.js provides built-in metadata APIs for managing page titles, descriptions, Open Graph tags, and other SEO-critical elements.
Static Metadata
// app/page.js - Export metadata object
export const metadata = {
title: "My Website",
description: "Welcome to my website",
};
export default function Page() {
return <h1>Welcome</h1>;
}
Full Metadata Options
export const metadata = {
// Basic
title: "My App",
description: "Description of my app",
keywords: ["Next.js", "React", "JavaScript"],
authors: [{ name: "John" }],
creator: "John Doe",
// Open Graph
openGraph: {
title: "My App",
description: "Description",
url: "https://example.com",
siteName: "My Site",
images: [
{
url: "https://example.com/og.jpg",
width: 1200,
height: 630,
alt: "My App",
},
],
locale: "en_US",
type: "website",
},
// Twitter
twitter: {
card: "summary_large_image",
title: "My App",
description: "Description",
images: ["https://example.com/og.jpg"],
},
// Icons
icons: {
icon: "/favicon.ico",
apple: "/apple-icon.png",
},
// Robots
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
},
},
// Canonical
alternates: {
canonical: "https://example.com",
},
};
Dynamic Metadata
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
};
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return <Article post={post} />;
}
Title Templates
// app/layout.js - Root layout with template
export const metadata = {
title: {
template: "%s | My Site",
default: "My Site",
},
};
// app/about/page.js
export const metadata = {
title: "About", // Results in: "About | My Site"
};
// Override template
export const metadata = {
title: {
absolute: "Custom Title", // Ignores template
},
};
JSON-LD Structured Data
export default function Page() {
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
name: "My Website",
url: "https://example.com",
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<h1>Welcome</h1>
</>
);
}
// Product structured data
const productJsonLd = {
"@context": "https://schema.org",
"@type": "Product",
name: "Product Name",
image: "https://example.com/product.jpg",
offers: {
"@type": "Offer",
price: "99.99",
priceCurrency: "USD",
availability: "https://schema.org/InStock",
},
};
Sitemap Generation
// app/sitemap.js
export default async function sitemap() {
const posts = await getPosts();
const postUrls = posts.map((post) => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: "weekly",
priority: 0.8,
}));
return [
{
url: "https://example.com",
lastModified: new Date(),
changeFrequency: "daily",
priority: 1,
},
{
url: "https://example.com/about",
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.5,
},
...postUrls,
];
}
Robots.txt
// app/robots.js
export default function robots() {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/admin/", "/private/"],
},
],
sitemap: "https://example.com/sitemap.xml",
};
}
Viewport and Theme Color
export const metadata = {
viewport: {
width: "device-width",
initialScale: 1,
maximumScale: 1,
},
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#000000" },
],
};
Metadata Inheritance
// app/layout.js - Base metadata
export const metadata = {
title: {
template: "%s | My Blog",
default: "My Blog",
},
description: "A blog about web development",
};
// app/blog/layout.js - Override for section
export const metadata = {
title: {
template: "%s | Blog | My Blog",
default: "Blog",
},
};
// app/blog/[slug]/page.js - Page-specific
export async function generateMetadata({ params }) {
return { title: "Post Title" };
// Results in: "Post Title | Blog | My Blog"
}
Key Points
- Export
metadataobject for static metadata - Use
generateMetadata()for dynamic metadata - Title templates with
%splaceholder - Include Open Graph and Twitter cards
- Add JSON-LD for rich snippets
- Generate sitemap.xml and robots.txt programmatically
- Metadata inherits and can be overridden per route