Next.js Metadata and SEO
Metadata is information about your page that browsers and search engines read but users do not see directly. It includes your page title, description, social media preview images, and canonical URLs. Next.js has a built-in metadata system that makes managing all of this straightforward.
Why Metadata Matters
Search engines use your page title and description to decide how to display your page in search results. Social media platforms read metadata to generate previews when someone shares your link. Missing or poor metadata means lower rankings and unattractive link previews.
GOOGLE SEARCH RESULT PREVIEW: ┌────────────────────────────────────────┐ │ yoursite.com/about │ │ About Us – Awesome Company │ ← title tag │ We build modern web apps for growing │ ← description tag │ businesses. Learn more about our team. │ └────────────────────────────────────────┘ TWITTER/FACEBOOK LINK PREVIEW: ┌────────────────────────────────────────┐ │ [OG Image / Social Preview Image] │ │ About Us – Awesome Company │ │ We build modern web apps... │ └────────────────────────────────────────┘
The Metadata Export
Export a metadata object from any page.js or layout.js file. Next.js reads this and injects the correct HTML tags into the <head> automatically.
// app/about/page.js
export const metadata = {
title: 'About Us – Awesome Company',
description: 'We build modern web applications for growing businesses.',
};
export default function AboutPage() {
return <main>...</main>;
}
This generates the following in your HTML head:
<title>About Us – Awesome Company</title> <meta name="description" content="We build modern web applications..." />
Root Layout Metadata (Default Metadata)
Set default metadata in app/layout.js. Every page inherits these defaults unless it exports its own metadata to override them.
// app/layout.js
export const metadata = {
title: {
default: 'My Website',
template: '%s | My Website',
},
description: 'Welcome to My Website – where great things happen.',
};
The template option adds your site name to every page title automatically. The %s placeholder is replaced by the individual page title.
Root template: '%s | My Website' About page title: 'About Us' Result in browser tab: 'About Us | My Website' Blog page title: 'Latest Posts' Result in browser tab: 'Latest Posts | My Website'
Open Graph Metadata for Social Sharing
Open Graph tags control how your page looks when shared on Facebook, LinkedIn, WhatsApp, and other platforms. Add them inside the metadata object.
export const metadata = {
title: 'About Us – Awesome Company',
description: 'We build modern web apps.',
openGraph: {
title: 'About Us – Awesome Company',
description: 'We build modern web applications for growing businesses.',
url: 'https://yoursite.com/about',
siteName: 'Awesome Company',
images: [
{
url: 'https://yoursite.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'Awesome Company team photo',
},
],
type: 'website',
},
};
Twitter Card Metadata
export const metadata = {
twitter: {
card: 'summary_large_image',
title: 'About Us – Awesome Company',
description: 'We build modern web apps.',
images: ['https://yoursite.com/twitter-image.jpg'],
},
};
Dynamic Metadata with generateMetadata
Static metadata works for fixed pages. For dynamic pages — like individual blog posts — you need metadata that changes based on the URL. Use the generateMetadata function for this.
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(res => res.json());
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: post.coverImage }],
},
};
}
export default async function PostPage({ params }) {
// Page component code...
}
Diagram: Static vs Dynamic Metadata
STATIC METADATA:
export const metadata = { title: 'About Us' }
→ Same for every visit → Good for fixed pages
DYNAMIC METADATA:
export async function generateMetadata({ params }) { ... }
→ Runs per request → Reads params → Fetches data → Returns metadata
→ Good for blog posts, product pages, user profiles
Robots and Canonical Tags
Control search engine crawling and set canonical URLs through the metadata object.
export const metadata = {
alternates: {
canonical: 'https://yoursite.com/about',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
},
},
};
The robots.txt and sitemap.xml Files
Next.js can generate these files automatically. Create a robots.js file in the app/ folder:
// app/robots.js
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/admin/',
},
sitemap: 'https://yoursite.com/sitemap.xml',
};
}
Create a sitemap.js file in the app/ folder:
// app/sitemap.js
export default function sitemap() {
return [
{
url: 'https://yoursite.com',
lastModified: new Date(),
},
{
url: 'https://yoursite.com/about',
lastModified: new Date(),
},
];
}
Key Takeaway
Export a metadata object from page and layout files to set titles, descriptions, and social sharing previews. Use the root layout for site-wide defaults and the template option to auto-append your site name. Use generateMetadata for pages with dynamic content. Add robots.js and sitemap.js files to help search engines discover and index your pages correctly.
