SEO in Next.js: Metadata API, Sitemaps and Open Graph Images
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.