Asaad Logo
SEO

Next.js SEO: Programmatically Manage Metadata for Better Rankings

Author image

Author

Asaad Mohamed

Date Published

AI SEO

Programmatic SEO: The Key to Scalable Optimization

Search Engine Optimization (SEO) isn't just about keywords and backlinks—it's about giving search engines the right information to understand and promote your content.

In modern web apps, especially those built with Next.js, we often generate pages dynamically. That means static metadata won’t cut it—we need a way to programmatically control SEO fields like the page title, description, Open Graph tags, and more.

In this guide, we’ll show you how to manage SEO metadata programmatically in both the Pages Router and App Router using real-world examples.

Why Metadata Matters

Metadata lives inside your HTML <head> and helps:

Search engines understand what your page is about.

Social media platforms (like Facebook, LinkedIn, Twitter) generate rich previews.

Users make informed decisions about clicking your links.

Without proper metadata, your content may never reach your intended audience—even if it's amazing.

App Router SEO: The Modern Way

Next.js 13+ introduced the App Router, a modern and powerful routing system with built-in support for programmatic SEO metadata—no more manual <Head> components.

In this post, you’ll learn how to manage dynamic metadata using generateMetadata() in the App Router to improve SEO, social media sharing, and search engine visibility.

What is Metadata in Next.js App Router?

In the App Router, each route can export:

metadata: for static values (title, description, etc.)

generateMetadata(): for dynamic, async metadata based on route params or fetched data

SEO Benefits:

Clean, declarative SEO config

Built-in Open Graph & Twitter support

Server-side fetching of SEO content

Better for CMS-powered or dynamic apps

Example 1: Static Metadata for Static Pages

Use the metadata export in simple static pages like About or Contact:

1// app/about/page.tsx
2
3export const metadata = {
4 title: 'About Us | My Site',
5 description: 'Learn about our mission, team, and values.',
6 openGraph: {
7 title: 'About Us | My Site',
8 description: 'Learn about our mission, team, and values.',
9 url: 'https://mysite.com/about',
10 },
11 twitter: {
12 card: 'summary',
13 },
14}
15
16export default function AboutPage() {
17 return <div>Welcome to the About Page!</div>
18}
19

No need to use <Head> or next/head — it’s all handled automatically.

Example 2: Dynamic Metadata for Dynamic Pages

For dynamic routes like blog posts or product pages, use generateMetadata() to pull metadata from a CMS or API.

1// app/blog/[slug]/page.tsx
2
3import { fetchPostBySlug } from '@/lib/api'
4
5export async function generateMetadata({ params }) {
6 const post = await fetchPostBySlug(params.slug)
7
8 if (!post) {
9 return {
10 title: 'Post Not Found | My Blog',
11 description: 'This blog post does not exist.',
12 }
13 }
14
15 return {
16 title: `${post.title} | My Blog`,
17 description: post.excerpt,
18 openGraph: {
19 title: post.title,
20 description: post.excerpt,
21 images: [post.image],
22 url: `https://myblog.com/blog/${params.slug}`,
23 },
24 twitter: {
25 card: 'summary_large_image',
26 title: post.title,
27 description: post.excerpt,
28 images: [post.image],
29 },
30 }
31}
32
33export default async function BlogPage({ params }) {
34 const post = await fetchPostBySlug(params.slug)
35
36 return (
37 <article className="prose max-w-2xl mx-auto">
38 <h1>{post.title}</h1>
39 <p>{post.content}</p>
40 </article>
41 )
42}
43

✨ Pro Tips

Add canonical URLs:

1return {
2 ...,
3 alternates: {
4 canonical: `https://myblog.com/blog/${params.slug}`,
5 },
6}
7

Use structured data (JSON-LD) by injecting a <script> manually in your component:

1<script
2 type="application/ld+json"
3 dangerouslySetInnerHTML={{
4 __html: JSON.stringify({
5 '@context': 'https://schema.org',
6 '@type': 'BlogPosting',
7 headline: post.title,
8 author: post.author.name,
9 datePublished: post.date,
10 image: post.image,
11 }),
12 }}
13/>
14

✅ Advanced: Canonical Tags & Structured Data

Canonical tags help avoid duplicate content penalties, while JSON-LD improves rich snippets.

1<Head>
2 <link rel="canonical" href={`https://myblog.com/blog/${post.slug}`} />
3 <script
4 type="application/ld+json"
5 dangerouslySetInnerHTML={{
6 __html: JSON.stringify({
7 '@context': 'https://schema.org',
8 '@type': 'BlogPosting',
9 headline: post.title,
10 image: post.image,
11 author: {
12 '@type': 'Person',
13 name: post.author,
14 },
15 datePublished: post.date,
16 }),
17 }}
18 />
19</Head>
20

Best Practices

Use generateMetadata() for dynamic content

Set Open Graph & Twitter tags

Add canonical URLs

Use structured data (JSON-LD)

Avoid duplicate titles/descriptions ❌

Final Thoughts

Next.js App Router gives you a robust, declarative way to manage SEO. Whether you're building a blog, an eCommerce site, or a SaaS dashboard, programmatic metadata ensures every page is:

Optimized for search engines

Shareable on social media

Configurable per route

Programmatic SEO isn’t just a nice-to-have—it’s a growth multiplier.

Need Help?

Want a reusable SEO utility or a boilerplate setup with Payload/Sanity CMS + dynamic SEO? Just ask! I’ll gladly help.

Comments2

Léo Dubois
Jun 10, 2025
James Wu
Jun 10, 2025

Leave a comment