Generate metadata, open graph tags, and improve SEO automatically.
import { Metadata } from "next";
export const metadata: Metadata = {
title: "About Us",
description: "Learn more about our company and mission.",
keywords: ["about", "company", "mission"]
};
export default function About() {
return <div>About content</div>;
}// app/layout.tsx
export const metadata: Metadata = {
title: { default: "My Site", template: "%s | My Site" },
description: "Default description for all pages."
};// app/about/page.tsx
export const metadata: Metadata = {
title: "About" // becomes "About | My Site" via template
};import { Metadata } from "next";
export async function generateMetadata({
params
}: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params; // params is a Promise in Next.js 15
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
authors: [{ name: post.author }]
};
}
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}export async function generateMetadata({ params }): Promise<Metadata> {
const product = await getProduct(params.id); // Next.js deduplicates this fetch
return { title: product.name, description: product.description };
}
export default async function Page({ params }) {
const product = await getProduct(params.id); // same fetch, cached
return <ProductDetail product={product} />;
}export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
if (!post) notFound();
return { title: post.title };
}export const metadata: Metadata = {
title: "My Post",
description: "Read my latest blog post",
openGraph: {
title: "My Post",
description: "Read my latest blog post",
url: "https://example.com/blog/my-post",
siteName: "My Blog",
images: [
{
url: "https://example.com/og-image.png",
width: 1200,
height: 630
}
],
type: "article"
}
};// app/og/route.tsx
import { ImageResponse } from "next/og";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
return new ImageResponse(<div>{searchParams.get("title")}</div>);
}openGraph: {
images: [`/og?title=${encodeURIComponent(post.title)}`]
}export const metadata: Metadata = {
title: "My Post",
description: "Read my latest blog post",
twitter: {
card: "summary_large_image",
title: "My Post",
description: "Read my latest blog post",
images: ["https://example.com/og-image.png"],
creator: "@myhandle"
}
};twitter: {
card: "summary_large_image",
site: "@mycompany",
creator: "@authorhandle"
}const ogImage = "https://example.com/og-image.png";
export const metadata: Metadata = {
openGraph: { images: [ogImage] },
twitter: { images: [ogImage] }
};export default async function BlogPost({
params
}: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: post.excerpt,
image: post.image,
author: {
"@type": "Person",
name: post.author
},
datePublished: post.date
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}const breadcrumb = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Home", item: "/" },
{ "@type": "ListItem", position: 2, name: "Blog", item: "/blog" }
]
};Export an async generateMetadata function from your page component that receives params and searchParams, fetches any needed data, and returns a metadata object with title and description fields. This runs server-side and generates unique metadata per route.
The exported metadata constant is for static values known at build time, while generateMetadata is an async function for values that depend on dynamic data like route params or database fetches. Use metadata when the values never change, generateMetadata when they do.
Include an openGraph object inside your metadata export or generateMetadata return, with fields like title, description, url, and images. The images array should contain objects with url, width: 1200, and height: 630 for correct social previews.
Render a <script type='application/ld+json'> tag inside your page component using JSX, with dangerouslySetInnerHTML set to __html: JSON.stringify(yourSchemaObject). Place it in the component's return alongside your content, not inside the metadata export.
Yes, add a twitter object to your metadata with fields like card, title, description, and images. If omitted, many platforms fall back to Open Graph values, but explicitly setting twitter gives you control over how your content appears specifically on X/Twitter.