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)
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.4---5
6# Next.js Metadata
7
8Expert guidance for implementing effective metadata in Next.js.
9
10## Quick Reference
11
12| 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' }` |
20
21## What Do You Need?
22
231. **Static metadata** - metadata object for fixed values
242. **Dynamic metadata** - generateMetadata for dynamic values
253. **OpenGraph** - Social sharing images and descriptions
264. **Structured data** - JSON-LD for rich results
275. **Canonical URLs** - Preventing duplicate content
28
29Specify a number or describe your metadata scenario.
30
31## Routing
32
33| 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) |
40
41## Essential Principles
42
43**Every page needs metadata**: Title, description, OpenGraph image minimum.
44
45**Static for static pages**: Use metadata object when values don't change.
46
47**Dynamic for dynamic routes**: Use generateMetadata when data comes from params or fetch.
48
49**OpenGraph for sharing**: Ensure pages look good when shared on social media.
50
51**Structured data for rich results**: Use JSON-LD for articles, products, organizations.
52
53## Code Patterns
54
55### Static Metadata
56```typescript
57// app/page.tsx
58import { Metadata } from 'next'
59
60export 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}
69
70export default function Page() {
71 return <div>...</div>
72}
73```
74
75### Dynamic Metadata
76```typescript
77// app/blog/[slug]/page.tsx
78import { Metadata } from 'next'
79
80export async function generateMetadata(
81 { params }: { params: { slug: string } }
82): Promise<Metadata> {
83 const post = await fetchPost(params.slug)
84
85 return {
86 title: post.title,
87 description: post.excerpt,
88 openGraph: {
89 title: post.title,
90 images: [post.ogImage],
91 },
92 }
93}
94
95export default function BlogPost({ params }: { params: { slug: string } }) {
96 // ...
97}
98```
99
100### Structured Data
101```typescript
102import Script from 'next/script'
103
104export 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 }
112
113 return (
114 <>
115 <Script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
116 <article>{post.content}</article>
117 </>
118 )
119}
120```
121
122## Metadata Checklist
123
124| 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> |
134
135## Common Issues
136
137| 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 |
145
146## Reference Index
147
148| 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 |
155
156## Success Criteria
157
158Metadata is complete when:
159- Every page has title and description
160- OpenGraph tags present (title, description, image)
161- OG image is 1200x630px minimum
162- Dynamic routes use generateMetadata
163- Canonical URLs set for duplicate content
164- Structured data for content types (articles, products)