Technology · Next.js
Next.js Metadata and SEO
Generate metadata, open graph tags, and improve SEO automatically.
TL;DR
- 01Export metadata constant to set page titles and descriptions.
- 02Use generateMetadata for dynamic metadata from data.
- 03Use Open Graph tags for social media sharing.
Static Metadata
- Export metadata constant for static pages.
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>; } - Metadata sets HTML head tags automatically.
- Improves SEO and social media sharing.
- Use a layout-level metadata object to apply defaults across all pages.
// app/layout.tsx export const metadata: Metadata = { title: { default: "My Site", template: "%s | My Site" }, description: "Default description for all pages." }; - Child page metadata merges with and overrides layout metadata.
// app/about/page.tsx export const metadata: Metadata = { title: "About" // becomes "About | My Site" via template };
Dynamic Metadata
- Generate metadata from data or parameters.
import { Metadata } from "next"; export async function generateMetadata({ params }): Promise<Metadata> { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt, authors: [{ name: post.author }] }; } export default async function Post({ params }) { const post = await getPost(params.slug); return <article>{post.content}</article>; } - generateMetadata runs on the server.
- Has access to params and other request data.
- Fetch and cache data inside generateMetadata to avoid duplicate requests.
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} />; } - Return notFound() inside generateMetadata to trigger a 404 page early.
export async function generateMetadata({ params }): Promise<Metadata> { const post = await getPost(params.slug); if (!post) notFound(); return { title: post.title }; }
Open Graph Tags
- Set Open Graph tags for social sharing.
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" } }; - Open Graph improves how links look on social media.
- Include images for better engagement.
- Use dynamic OG images with Next.js ImageResponse for per-page previews.
// 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>); } - Reference the dynamic OG route in your metadata image field.
openGraph: { images: [`/og?title=${encodeURIComponent(post.title)}`] }
Twitter Card Tags
- Add Twitter Card metadata for tweets.
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 Cards make tweets more engaging.
- Use summary_large_image for best results.
- Set twitter.site to your handle for attribution on shared links.
twitter: { card: "summary_large_image", site: "@mycompany", creator: "@authorhandle" } - Reuse the same image URL for both OG and Twitter to reduce duplication.
const ogImage = "https://example.com/og-image.png"; export const metadata: Metadata = { openGraph: { images: [ogImage] }, twitter: { images: [ogImage] } };
Structured Data
- Add JSON-LD structured data for search engines.
export default function BlogPost({ params }) { const post = getPost(params.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) }} /> ); } - Structured data helps search engines understand content.
- Use schema.org types for consistency.
- Add BreadcrumbList schema to help search engines show site hierarchy.
const breadcrumb = { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ { "@type": "ListItem", position: 1, name: "Home", item: "/" }, { "@type": "ListItem", position: 2, name: "Blog", item: "/blog" } ] }; - Validate structured data with Google's Rich Results Test tool before deploying.
Tip: Use generateMetadata with dynamic data to create unique, SEO-friendly titles and descriptions for each page.
Warning: Always include Open Graph images with correct dimensions (1200x630) to ensure proper display on social media.