SEO in Next.js: Metadata API, Sitemaps and Open Graph Images

A
Author
20 Jun, 2026 2 min read 1.35K views

The App Router has built-in tools for everything search engines and social networks need: titles, descriptions, canonical links, sitemaps and share images.

Static metadata

// app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: { default: 'Acme Docs', template: '%s | Acme Docs' },
  description: 'Guides and API reference for Acme.',
};

Dynamic metadata per page

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
  return {
    title: post.title, // becomes "My Post | Acme Docs"
    description: post.excerpt,
    alternates: { canonical: `/blog/${slug}` },
    openGraph: { type: 'article', publishedTime: post.publishedAt, images: [post.coverUrl] },
  };
}

If generateMetadata and the page fetch the same data, wrap the loader in React's cache so it runs once.

sitemap.xml and robots.txt

// app/sitemap.ts
import type { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts();
  return [
    { url: 'https://example.com', changeFrequency: 'daily', priority: 1 },
    ...posts.map((p) => ({ url: `https://example.com/blog/${p.slug}`, lastModified: p.updatedAt })),
  ];
}
// app/robots.ts
export default function robots() {
  return { rules: { userAgent: '*', allow: '/', disallow: '/admin' }, sitemap: 'https://example.com/sitemap.xml' };
}

Generated share images

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';

export const size = { width: 1200, height: 630 };

export default async function Image({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  return new ImageResponse(
    <div style={{ fontSize: 64, background: '#0f172a', color: 'white', width: '100%', height: '100%', display: 'flex', padding: 64 }}>
      {post.title}
    </div>,
    size
  );
}

Checklist

  • Unique title and description on every page.
  • Canonical URL on pages reachable by several URLs.
  • Real 404 status for missing content (notFound()).
  • Structured data (JSON-LD) for articles, products and breadcrumbs.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.