Home Quizzes Leaderboard Competitions Learn Hire Us
About Contact
Log In Sign Up
Learn Next.js Metadata, SEO & the Head

Metadata, SEO & the Head

⏱ 12 min read read
Static Metadata --- Export an Object:

// app/about/page.tsx

import { Metadata } from 'next';

export const metadata: Metadata = {

title: 'About Us --- My App',

description: 'Learn about our mission and team.',

keywords: ['about', 'team', 'company'],

openGraph: {

title: 'About Us',

description: 'Learn about our mission.',

url: 'https://myapp.com/about',

images: [{ url: 'https://myapp.com/og.png', width: 1200, height:
630 }],

type: 'website',

},

twitter: {

card: 'summary_large_image',

title: 'About Us',

},

};

Dynamic Metadata --- generateMetadata Function:

// app/blog/[slug]/page.tsx

export async function generateMetadata(

{ params }: { params: { slug: string } }

): Promise<Metadata> {

const post = await getPostBySlug(params.slug);

if (!post) return { title: 'Post Not Found' };

return {

title: `${post.title} --- My Blog`,

description: post.excerpt,

openGraph: { title: post.title, images: [{ url: post.ogImage }] },

};

}

Title Template --- Avoid Repeating Site Name:

// app/layout.tsx

export const metadata: Metadata = {

title: {

default: 'My App',

template: '%s --- My App', // '%s' is replaced by page title

},

description: 'The best app ever.',

};

// app/about/page.tsx --- just set 'About Us'

export const metadata: Metadata = { title: 'About Us' };

// → renders as 'About Us --- My App'

Next.js 13+ handles all <head> tags via the Metadata API --- no
next/head needed.

Metadata is automatically deduped and merged from layouts → pages.

Use the Next.js favicon.ico file convention: place in
app/favicon.ico.

Robots, sitemap: app/robots.ts and app/sitemap.ts for dynamic
generation.
Code Example
// app/layout.tsx --- root metadata with title template

import { Metadata } from 'next';

export const metadata: Metadata = {

title: {

default: 'PHPAcademy',

template: '%s | PHPAcademy',

},

description: 'Learn programming with interactive lessons.',

metadataBase: new URL('https://phpacademy.dev'),

openGraph: {

siteName: 'PHPAcademy',

type: 'website',

},

robots: { index: true, follow: true },

};

// app/courses/[slug]/page.tsx --- dynamic metadata

type Course = { title: string; description: string; image: string };

export async function generateMetadata(

{ params }: { params: { slug: string } }

): Promise<Metadata> {

const course: Course = await fetch(

`https://api.example.com/courses/${params.slug}`

).then(r => r.json());

return {

title: course.title, // → 'Python Basics | PHPAcademy'

description: course.description,

openGraph: {

title: course.title,

images: [{ url: course.image, width: 1200, height: 630 }],

},

twitter: {

card: 'summary_large_image',

title: course.title,

description: course.description,

images: [course.image],

},

};

}

// app/sitemap.ts --- auto-generated XML sitemap

import { MetadataRoute } from 'next';

export default async function sitemap():
Promise<MetadataRoute.Sitemap> {

const courses = await getAllCourses();

return [

{ url: 'https://example.com', lastModified: new Date() },

...courses.map(c => ({

url: `https://example.com/courses/${c.slug}`,

lastModified: c.updatedAt,

})),

];

}
← Authentication with NextAuth.js Images, Fonts & Static Assets →

Log in to track your progress and earn badges as you complete lessons.

Log In to Track Progress