Blog/Quick Tips & Snippets/Master Next.js Metadata API for Better SEO
POST
March 18, 2025
LAST UPDATEDMarch 18, 2025

Master Next.js Metadata API for Better SEO

Learn how to use the Next.js Metadata API to boost SEO with dynamic titles, Open Graph tags, and structured data in the App Router.

Tags

Next.jsSEOMetadataApp Router
Master Next.js Metadata API for Better SEO
4 min read

Master Next.js Metadata API for Better SEO

This is part of the AI Automation Engineer Roadmap series.

TL;DR

Use the generateMetadata function in Next.js App Router to create dynamic, per-page SEO metadata with full type safety.

Why This Matters

Dynamic pages like blog posts and product pages each need unique titles, descriptions, and Open Graph images. Hardcoding a static metadata export doesn't work when the content comes from a database or CMS. You need a way to fetch data and generate metadata dynamically for each route.

The Metadata API matters because it centralizes core technical SEO concerns in a framework-native way:

  • page titles and descriptions
  • canonical URLs
  • Open Graph and Twitter cards
  • robots directives
  • alternate language references
  • route-specific metadata for dynamic pages

If you misuse it, your pages can end up with duplicated titles, broken social previews, or stale metadata on key content routes. If you use it correctly, you get a cleaner implementation and better consistency across the app.

Static vs Dynamic Metadata

Next.js gives you two main patterns:

  • export const metadata for static route metadata
  • export async function generateMetadata() for dynamic metadata

Use the static export when the route content is fixed, such as an about page or contact page. Use generateMetadata when metadata depends on fetched data, route params, or content files.

Static Example

tsx
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: "Contact",
  description: "Get in touch for web development and AI automation work.",
};
 
export default function ContactPage() {
  return <main>...</main>;
}

Dynamic Example

Export an async generateMetadata function from your page file:

tsx
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
 
type Props = {
  params: Promise<{ slug: string }>;
};
 
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await fetch(`https://api.example.com/posts/${slug}`).then((r) =>
    r.json()
  );
 
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage, width: 1200, height: 630 }],
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
      description: post.excerpt,
    },
    alternates: {
      canonical: `https://yoursite.com/blog/${slug}`,
    },
  };
}
 
export default async function BlogPost({ params }: Props) {
  const { slug } = await params;
  // render post...
}

Next.js deduplicates the fetch call, so generateMetadata and your page component share the same request without any extra work.

What Metadata You Should Usually Set

For most content pages, the useful baseline is:

  • title
  • description
  • alternates.canonical
  • openGraph.title
  • openGraph.description
  • openGraph.url
  • openGraph.images
  • twitter.card
  • robots

If the page is indexable and shareable, those fields do most of the work.

tsx
return {
  title: post.title,
  description: post.excerpt,
  alternates: {
    canonical: `https://yoursite.com/blog/${slug}`,
  },
  openGraph: {
    type: "article",
    url: `https://yoursite.com/blog/${slug}`,
    title: post.title,
    description: post.excerpt,
    images: [{ url: post.coverImage, width: 1200, height: 630 }],
  },
  twitter: {
    card: "summary_large_image",
    title: post.title,
    description: post.excerpt,
    images: [post.coverImage],
  },
  robots: {
    index: true,
    follow: true,
  },
};

Common Patterns That Work Well

1. Share SEO Data Between the Page and Metadata

If you fetch the same content in the page and in generateMetadata, keep the data-loading logic in one reusable function.

tsx
async function getPost(slug: string) {
  return fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 3600 },
  }).then((r) => r.json());
}
 
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
 
  return {
    title: post.title,
    description: post.excerpt,
  };
}
 
export default async function BlogPost({ params }: Props) {
  const { slug } = await params;
  const post = await getPost(slug);
 
  return <article>{post.title}</article>;
}

This keeps metadata and rendered content aligned.

2. Return a Fallback When Content Is Missing

Do not assume the content always exists. If a slug is invalid or a CMS item is unpublished, your metadata should still behave predictably.

tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
 
  if (!post) {
    return {
      title: "Post Not Found",
      description: "The requested article could not be found.",
      robots: { index: false, follow: false },
    };
  }
 
  return {
    title: post.title,
    description: post.excerpt,
  };
}

3. Use Canonicals Intentionally

Canonical URLs are especially important when:

  • query params create multiple URL variants
  • filtered pages are crawlable
  • content is syndicated
  • you have both category and tag routes

The Metadata API makes it easy to enforce one primary URL per page.

Where the Metadata API Ends

The Metadata API handles head tags. It does not replace everything related to SEO.

You still need:

  • good on-page copy
  • structured data where appropriate
  • solid internal linking
  • proper robots and sitemap handling
  • clean heading hierarchy

A lot of teams expect metadata alone to fix discoverability. It will not. It is necessary, but not sufficient.

Common Mistakes

Treating Metadata as a One-Time Setup

Metadata should reflect the route. A single global title template is useful, but dynamic pages still need unique per-item values.

Forgetting Social Images

Pages without route-specific Open Graph images often look generic when shared. If the page matters for discovery or distribution, the preview asset matters too.

Mismatching Page Content and Metadata

If the title tag says one thing and the on-page heading says another, search engines and users get mixed signals.

Building Canonicals Incorrectly

A canonical URL should be the final preferred URL. Accidentally using a relative path, a staging domain, or the wrong slug is a common mistake.

Production Recommendations

If you are shipping a content-heavy Next.js app, the clean pattern is:

  1. centralize site-level constants
  2. keep route-level metadata generation close to content loading
  3. use route-specific Open Graph images where possible
  4. set explicit canonical URLs
  5. combine Metadata API with JSON-LD for key pages

That gives you a strong technical foundation without scattering SEO logic across the app.

Why This Works

The generateMetadata function runs on the server at request time (or build time for static routes) and returns a typed Metadata object. Next.js takes this object and renders the correct <title>, <meta>, and <link> tags in the document head. Because it's a standard async function, you can fetch data, read from a database, or compute values -- all with full TypeScript autocomplete on the return type. The framework handles deduplication, streaming, and proper tag ordering automatically.

Final Takeaway

The Next.js Metadata API is the right default for App Router SEO. Use static metadata for fixed routes, generateMetadata for dynamic content, and pair it with canonical URLs, social previews, and structured data for the routes that actually matter.

Collaboration

Need help with a project?

Let's Build It

I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI — let's bring your vision to life.

SH

Article Author

Sadam Hussain

Senior Full Stack Developer

Senior Full Stack Developer with over 7 years of experience building React, Next.js, Node.js, TypeScript, and AI-powered web platforms.

Related Articles

TypeScript Utility Types You Should Know
Feb 10, 20263 min read
TypeScript
Cheatsheet

TypeScript Utility Types You Should Know

Five essential built-in generic utility types in TypeScript that will save you hundreds of lines of code.

Generate Dynamic OG Images in Next.js
Feb 08, 20262 min read
Next.js
OG Images
SEO

Generate Dynamic OG Images in Next.js

Generate dynamic Open Graph images in Next.js using the ImageResponse API with custom fonts, gradients, and data-driven content for social sharing.

GitHub Actions Reusable Workflows: Stop Repeating Yourself
Jan 22, 20263 min read
GitHub Actions
CI/CD
DevOps

GitHub Actions Reusable Workflows: Stop Repeating Yourself

Create reusable GitHub Actions workflows with inputs, secrets, and outputs to eliminate YAML duplication across repositories and teams efficiently.