Skip to main content

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 metadata object for static metadata
  • Use generateMetadata() for dynamic metadata
  • Title templates with %s placeholder
  • 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