Set page titles and descriptions statically or from data, add icons and OG images, and generate a sitemap and robots file.
Why: export a metadata object from a page or layout and Next.js writes the matching <head> tags (title, description, and more) for you. This is what search engines and link previews read. Note: "metadata" is data about the page, not shown on it.
// app/blog/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: 'Articles about web development.',
}
export default function Page() {
return <h1>The Blog</h1>
}Why: when the title depends on the page’s data (a post’s title), export an async generateMetadata function instead. It receives the same params as the page and returns the metadata object.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((r) => r.json())
return { title: post.title, description: post.description }
}Why: generateMetadata and the page often need the same data. Wrap the fetch in React’s cache() so it runs once per request even when called from both places. Note: cache() here de-duplicates within a single request.
// app/lib/data.ts
import { cache } from 'react'
export const getPost = cache(async (slug: string) => {
return db.query.posts.findFirst({ where: eq(posts.slug, slug) })
})
// Now call getPost(slug) in BOTH generateMetadata and the page —
// the database is queried only once.Why: drop specially-named files into app/ and Next.js wires up the tags automatically. favicon.ico becomes the tab icon; opengraph-image.png becomes the preview image shown when your link is shared on social media.
// Just add these files — no code needed:
// app/favicon.ico -> browser tab icon
// app/icon.png -> app icon
// app/opengraph-image.png -> social share preview (1200x630 recommended)
// A deeper file wins: app/blog/opengraph-image.png overrides the root one.Why: a sitemap.ts lists your URLs for search engines; a robots.ts tells crawlers what they may visit. Both are code files that return data, so you can build them from your real routes.
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://example.com', lastModified: new Date() },
{ url: 'https://example.com/blog', lastModified: new Date() },
]
}
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return { rules: { userAgent: '*', allow: '/' } }
}