← Back to chapters
16advanced

Metadata & SEO

Page titles, Open Graph images, structured data, sitemaps, and robots.txt for discoverability.

Metadata & SEO: Get Found on Google

Your app could be the best in the world, but if Google can't understand it, nobody finds it. Next.js gives you powerful tools to control exactly how your pages appear in search results, social media shares, and browser tabs.

Static Metadata

The simplest approach. Export a metadata object from any layout or page. It gets merged with parent metadata automatically.

src/app/blog/page.tsx
tsx
1import type { Metadata } from "next"
2
3export const metadata: Metadata = {
4title: "My Blog | Prasen",
5description: "Thoughts on web development, React, and Next.js",
6keywords: ["blog", "react", "nextjs", "web development"],
7authors: [{ name: "Prasen", url: "https://prasen.dev" }],
8openGraph: {
9 title: "My Blog | Prasen",
10 description: "Thoughts on web development",
11 url: "https://prasen.dev/blog",
12 siteName: "Prasen's Blog",
13 images: [
14 {
15 url: "/og-image.png",
16 width: 1200,
17 height: 630,
18 alt: "Blog preview image",
19 },
20 ],
21 type: "website",
22},
23twitter: {
24 card: "summary_large_image",
25 title: "My Blog | Prasen",
26 description: "Thoughts on web development",
27 images: ["/og-image.png"],
28},
29}
30
31export default function BlogPage() {
32return <main>...</main>
33}

Dynamic Metadata

For pages with dynamic content (blog posts, products), use generateMetadata. It receives the same params as your page component.

src/app/blog/[slug]/page.tsx
tsx
1import type { Metadata } from "next"
2
3interface Props {
4params: Promise<{ slug: string }>
5}
6
7export async function generateMetadata({ params }: Props): Promise<Metadata> {
8const { slug } = await params
9const post = await getPost(slug)
10
11return {
12 title: post.title,
13 description: post.excerpt,
14 openGraph: {
15 title: post.title,
16 description: post.excerpt,
17 images: [post.coverImage],
18 },
19}
20}
21
22export default async function PostPage({ params }: Props) {
23const { slug } = await params
24const post = await getPost(slug)
25return <article>{post.content}</article>
26}

Metadata Merging Rules

Next.js automatically merges metadata from parent layouts to child pages. Child values override parent values. This means you set defaults in your root layout and override per-page as needed.

šŸš€

Template titles

Use the title template pattern for consistent page titles:
• Root layout: title: { template: '%s | My Site', default: 'My Site' }
• Child page: title: 'About' renders as 'About | My Site'
• Keeps your brand consistent without repeating the suffix everywhere

Sitemaps & robots.txt

Next.js can generate these automatically. Create a sitemap.ts and robots.ts file in your app directory.

src/app/sitemap.ts
tsx
1import type { MetadataRoute } from "next"
2
3export default function sitemap(): MetadataRoute.Sitemap {
4const posts = await getAllPosts()
5
6return [
7 { url: "https://prasen.dev", lastModified: new Date() },
8 { url: "https://prasen.dev/blog", lastModified: new Date() },
9 ...posts.map((post) => ({
10 url: `https://prasen.dev/blog/${post.slug}`,
11 lastModified: post.updatedAt,
12 })),
13]
14}
src/app/robots.ts
tsx
1import type { MetadataRoute } from "next"
2
3export default function robots(): MetadataRoute.Robots {
4return {
5 rules: {
6 userAgent: "*",
7 allow: "/",
8 disallow: ["/api/", "/admin/"],
9 },
10 sitemap: "https://prasen.dev/sitemap.xml",
11}
12}

JSON-LD Structured Data

For rich search results (star ratings, FAQ snippets, breadcrumbs), add JSON-LD structured data to your pages.

src/app/blog/[slug]/page.tsx
tsx
1export default function BlogPost({ post }) {
2const jsonLd = {
3 "@context": "https://schema.org",
4 "@type": "BlogPosting",
5 headline: post.title,
6 author: {
7 "@type": "Person",
8 name: "Prasen",
9 url: "https://prasen.dev",
10 },
11 datePublished: post.publishedAt,
12 image: post.coverImage,
13}
14
15return (
16 <>
17 <script
18 type="application/ld+json"
19 dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
20 />
21 <article>{post.content}</article>
22 </>
23)
24}
šŸ’”

SEO checklist for every page

Before shipping:
• Unique title and description
• Open Graph image (1200x630px)
• Proper heading hierarchy (h1 > h2 > h3)
• Alt text on all images
• Fast load time (LCP < 2.5s)
• Mobile-friendly layout
• Canonical URL if content is duplicated

ā–¶Watch and Learn