Next.js Metadata
Expert guidance for implementing effective metadata in Next.js.
Quick Reference
| Concern |
Solution |
Example |
| Page title |
metadata object |
export const metadata = { title: '...' } |
| Dynamic metadata |
generateMetadata function |
export async function generateMetadata({ params }) |
| OpenGraph images |
metadata.images |
openGraph: { images: ['/og.jpg'] } |
| Structured data |
Script component |
<Script type="application/ld+json"> |
| Canonical URLs |
metadata.alternates |
canonical: 'https://...' |
| Twitter cards |
metadata.twitter |
twitter: { card: 'summary_large_image' } |
What Do You Need?
- Static metadata - metadata object for fixed values
- Dynamic metadata - generateMetadata for dynamic values
- OpenGraph - Social sharing images and descriptions
- Structured data - JSON-LD for rich results
- Canonical URLs - Preventing duplicate content
Specify a number or describe your metadata scenario.
Routing
| Response |
Reference to Read |
| 1, "static", "metadata", "object" |
static-metadata.md |
| 2, "dynamic", "generatemetadata", "params" |
dynamic-metadata.md |
| 3, "opengraph", "social", "sharing" |
opengraph.md |
| 4, "structured", "json-ld", "schema" |
structured-data.md |
| 5, "canonical", "seo", "duplicate" |
seo.md |
Essential Principles
Every page needs metadata: Title, description, OpenGraph image minimum.
Static for static pages: Use metadata object when values don't change.
Dynamic for dynamic routes: Use generateMetadata when data comes from params or fetch.
OpenGraph for sharing: Ensure pages look good when shared on social media.
Structured data for rich results: Use JSON-LD for articles, products, organizations.
Code Patterns
Static Metadata
// app/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
description: 'Description for search engines',
openGraph: {
title: 'My App',
description: 'Description for social sharing',
images: ['/og-image.jpg'],
},
}
export default function Page() {
return <div>...</div>
}
Dynamic Metadata
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await fetchPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [post.ogImage],
},
}
}
export default function BlogPost({ params }: { params: { slug: string } }) {
// ...
}
Structured Data
import Script from 'next/script'
export default function ArticlePage({ post }: { post: Post }) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.publishedAt,
author: { '@type': 'Person', name: post.author.name },
}
return (
<>
<Script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<article>{post.content}</article>
</>
)
}
Metadata Checklist
| Element |
Required |
Format |
| title |
Yes |
string or Metadata.title |
| description |
Yes |
string (~160 chars) |
| openGraph:title |
Yes |
string |
| openGraph:description |
Yes |
string |
| openGraph:image |
Yes |
string or array (1200x630px min) |
| twitter:card |
Recommended |
'summary_large_image' |
| canonical |
Recommended |
string |
| alternates:languages |
Optional |
Record<locale, string> |
Common Issues
| Issue |
Severity |
Fix |
| Missing page title |
High |
Add metadata.title |
| No OpenGraph image |
Medium |
Add openGraph.images |
| No description |
Medium |
Add metadata.description |
| Dynamic not using generateMetadata |
High |
Change to async function |
| Duplicate content (no canonical) |
Medium |
Add metadata.canonical |
| Small OG image (< 1200x630) |
Low |
Use larger image |
Reference Index
| File |
Topics |
| static-metadata.md |
metadata object, all options |
| dynamic-metadata.md |
generateMetadata, params, fetch |
| opengraph.md |
OG tags, Twitter cards, images |
| structured-data.md |
JSON-LD, Script component, schemas |
| seo.md |
Canonical, hreflang, robots, sitemap |
Success Criteria
Metadata is complete when:
- Every page has title and description
- OpenGraph tags present (title, description, image)
- OG image is 1200x630px minimum
- Dynamic routes use generateMetadata
- Canonical URLs set for duplicate content
- Structured data for content types (articles, products)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: nextjs-metadata3description: Next.js Metadata API for SEO, OpenGraph tags, structured data, and social sharing. Use when implementing metadata, SEO, or social media previews. Use when this capability is needed.4---56# Next.js Metadata78Expert guidance for implementing effective metadata in Next.js.910## Quick Reference1112| Concern | Solution | Example |13|---------|----------|---------|14| Page title | metadata object | `export const metadata = { title: '...' }` |15| Dynamic metadata | generateMetadata function | `export async function generateMetadata({ params })` |16| OpenGraph images | metadata.images | `openGraph: { images: ['/og.jpg'] }` |17| Structured data | Script component | `<Script type="application/ld+json">` |18| Canonical URLs | metadata.alternates | `canonical: 'https://...'` |19| Twitter cards | metadata.twitter | `twitter: { card: 'summary_large_image' }` |2021## What Do You Need?22231. **Static metadata** - metadata object for fixed values242. **Dynamic metadata** - generateMetadata for dynamic values253. **OpenGraph** - Social sharing images and descriptions264. **Structured data** - JSON-LD for rich results275. **Canonical URLs** - Preventing duplicate content2829Specify a number or describe your metadata scenario.3031## Routing3233| Response | Reference to Read |34|----------|-------------------|35| 1, "static", "metadata", "object" | [static-metadata.md](./references/static-metadata.md) |36| 2, "dynamic", "generatemetadata", "params" | [dynamic-metadata.md](./references/dynamic-metadata.md) |37| 3, "opengraph", "social", "sharing" | [opengraph.md](./references/opengraph.md) |38| 4, "structured", "json-ld", "schema" | [structured-data.md](./references/structured-data.md) |39| 5, "canonical", "seo", "duplicate" | [seo.md](./references/seo.md) |4041## Essential Principles4243**Every page needs metadata**: Title, description, OpenGraph image minimum.4445**Static for static pages**: Use metadata object when values don't change.4647**Dynamic for dynamic routes**: Use generateMetadata when data comes from params or fetch.4849**OpenGraph for sharing**: Ensure pages look good when shared on social media.5051**Structured data for rich results**: Use JSON-LD for articles, products, organizations.5253## Code Patterns5455### Static Metadata56```typescript57// app/page.tsx58import { Metadata } from 'next'5960export const metadata: Metadata = {61 title: 'My App',62 description: 'Description for search engines',63 openGraph: {64 title: 'My App',65 description: 'Description for social sharing',66 images: ['/og-image.jpg'],67 },68}6970export default function Page() {71 return <div>...</div>72}73```7475### Dynamic Metadata76```typescript77// app/blog/[slug]/page.tsx78import { Metadata } from 'next'7980export async function generateMetadata(81 { params }: { params: { slug: string } }82): Promise<Metadata> {83 const post = await fetchPost(params.slug)8485 return {86 title: post.title,87 description: post.excerpt,88 openGraph: {89 title: post.title,90 images: [post.ogImage],91 },92 }93}9495export default function BlogPost({ params }: { params: { slug: string } }) {96 // ...97}98```99100### Structured Data101```typescript102import Script from 'next/script'103104export default function ArticlePage({ post }: { post: Post }) {105 const jsonLd = {106 '@context': 'https://schema.org',107 '@type': 'Article',108 headline: post.title,109 datePublished: post.publishedAt,110 author: { '@type': 'Person', name: post.author.name },111 }112113 return (114 <>115 <Script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />116 <article>{post.content}</article>117 </>118 )119}120```121122## Metadata Checklist123124| Element | Required | Format |125|---------|----------|--------|126| title | Yes | string or Metadata.title |127| description | Yes | string (~160 chars) |128| openGraph:title | Yes | string |129| openGraph:description | Yes | string |130| openGraph:image | Yes | string or array (1200x630px min) |131| twitter:card | Recommended | 'summary_large_image' |132| canonical | Recommended | string |133| alternates:languages | Optional | Record<locale, string> |134135## Common Issues136137| Issue | Severity | Fix |138|-------|----------|-----|139| Missing page title | High | Add metadata.title |140| No OpenGraph image | Medium | Add openGraph.images |141| No description | Medium | Add metadata.description |142| Dynamic not using generateMetadata | High | Change to async function |143| Duplicate content (no canonical) | Medium | Add metadata.canonical |144| Small OG image (< 1200x630) | Low | Use larger image |145146## Reference Index147148| File | Topics |149|------|--------|150| [static-metadata.md](./references/static-metadata.md) | metadata object, all options |151| [dynamic-metadata.md](./references/dynamic-metadata.md) | generateMetadata, params, fetch |152| [opengraph.md](./references/opengraph.md) | OG tags, Twitter cards, images |153| [structured-data.md](./references/structured-data.md) | JSON-LD, Script component, schemas |154| [seo.md](./references/seo.md) | Canonical, hreflang, robots, sitemap |155156## Success Criteria157158Metadata is complete when:159- Every page has title and description160- OpenGraph tags present (title, description, image)161- OG image is 1200x630px minimum162- Dynamic routes use generateMetadata163- Canonical URLs set for duplicate content164- Structured data for content types (articles, products)165166---167> Converted and distributed by [TomeVault](https://tomevault.io/claim/jovermier) — claim your Tome and manage your conversions.168<!-- tomevault:4.0:skill_md:2026-04-13 -->