Metadata, SEO and OG images

Next.js · Practical · 11 min

Correct metadata decides how pages appear in search results and link previews.

Next.js manages <head> through the Metadata API, only in Server Components (layouts and pages):

  • Static: export const metadata: Metadata = { title: 'About', description: '...' }.
  • Dynamic: export async function generateMetadata({ params }) returns metadata based on data, e.g. a blog post's title. Fetches inside it are memoised with the page's identical requests.
  • Metadata merges from the root layout down; a page overrides fields of its parents.
  • Title templates: in a layout, title: { template: '%s | Acme', default: 'Acme' } turns a page's title: 'Pricing' into "Pricing | Acme".
  • openGraph, twitter, alternates.canonical, robots and icons cover social previews and SEO. Set metadataBase in the root layout so relative URLs resolve to absolute ones.

File conventions generate assets and routes:

  • opengraph-image.tsx (or .png) creates the social preview image; dynamic ones use ImageResponse from next/og to render JSX to a PNG.
  • icon.png, apple-icon.png, favicon.ico in app/ become icons.
  • sitemap.ts and robots.ts export functions returning typed objects (MetadataRoute.Sitemap, MetadataRoute.Robots) that become /sitemap.xml and /robots.txt.

Use generateViewport or export const viewport for viewport and theme colour settings.

Example

// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
  metadataBase: new URL('https://acme.com'),
  title: { template: '%s | Acme', default: 'Acme' },
  description: 'Tools for builders',
}

// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/posts'
export async function generateMetadata({ params }: PageProps<'/blog/[slug]'>): Promise<Metadata> {
  const post = await getPost((await params).slug)
  return {
    title: post.title,                               // -> "My Post | Acme"
    description: post.excerpt,
    openGraph: { type: 'article', images: [post.coverUrl] },
  }
}

// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
  return [{ url: 'https://acme.com', lastModified: new Date() }]
}

Remember

  • Export metadata or generateMetadata from layouts and pages (server only)
  • Metadata merges top-down; title templates add a site suffix
  • opengraph-image, icon, sitemap.ts, robots.ts are file conventions

Watch out for

  • Adding <title> tags manually in components or exporting metadata from a Client Component; use the Metadata API in server files.

Check yourself

  1. The root layout sets title: { template: '%s | Acme', default: 'Acme' } and a page sets title: 'Pricing'. What is the document title?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/getting-started/metadata-and-og-images

Builds on

Unlocks

Nothing builds on this yet.