Next.js SEO optimization guide. Use when building Next.js apps, optimizing for search engines, fixing Google indexing issues, implementing metadata, sitemaps,…
Next.js SEO Optimization
Comprehensive SEO guide for Next.js App Router applications.
Quick SEO Audit
Run this checklist for any Next.js project:
Check robots.txt: curl https://your-site.com/robots.txt
Check sitemap: curl https://your-site.com/sitemap.xml
Check metadata: View page source, search for <title> and <meta name="description">
Check JSON-LD: View page source, search for application/ld+json
Check Core Web Vitals: Run Lighthouse in Chrome DevTools
Essential Files
app/layout.tsx - Root Metadata
import type { Metadata, Viewport } from 'next';
// Viewport must be a separate export — `themeColor`, `colorScheme`, and
// `viewport` inside the `metadata` object are not supported.
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 5,
userScalable: true,
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
],
};
export const metadata: Metadata = {
metadataBase: new URL('https://your-site.com'),
title: {
default: 'Site Title - Main Keyword',
template: '%s | Site Name',
},
description: 'Compelling description with keywords (150-160 chars; Google typically displays this range)',
keywords: ['keyword1', 'keyword2', 'keyword3'],
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://your-site.com',
siteName: 'Site Name',
title: 'Site Title',
description: 'Description for social sharing',
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'Site preview' }],
},
twitter: {
card: 'summary_large_image',
title: 'Site Title',
description: 'Description for Twitter',
images: ['/og-image.png'],
},
alternates: {
canonical: '/',
},
robots: {
index: true,
follow: true,
},
};
app/sitemap.ts - Dynamic Sitemap
import type { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://your-site.com';
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
images: [`${baseUrl}/og-image.png`], // Image Sitemap entry
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
];
}
app/robots.ts - Robots Configuration
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://your-site.com';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
// Do NOT disallow /_next/ — crawlers need render-critical CSS/JS
// Do NOT add bot-specific rules (Googlebot, Bingbot) unless overriding wildcard
},
],
sitemap: `${baseUrl}/sitemap.xml`,
host: baseUrl,
};
}
Key Principles
Cache Components & SEO
With cacheComponents: true in next.config.ts, use the "use cache" directive for SEO-critical server components:
// app/(home)/sections/hero-section.tsx
export async function HeroSection() {
"use cache";
cacheLife("minutes"); // Built-in profile: ~15 min
cacheTag("hero"); // For targeted invalidation via revalidateTag("hero")
const data = await fetchData();
return <div>{/* SEO-visible content */}</div>;
}
Key rules:
"use cache" must be the first statement in the function body
No cookies()/headers() inside cache scope
Use cacheLife() + cacheTag() instead of export const revalidate
Sitemaps and metadata are static by default — only use "use cache" if they fetch dynamic data
Rendering Strategy for SEO
Strategy
Use When
SEO Impact
"use cache"
Server components with periodic data
Best - cached HTML, fast TTFB
SSG (Static)
Content rarely changes
Best - pre-rendered HTML
SSR
Dynamic content per request
Great - server-rendered
CSR
Dashboards, authenticated areas
Poor - avoid for SEO pages
Core Web Vitals Targets
Metric
Target
Impact
LCP (Largest Contentful Paint)
< 2.5s
Loading speed
INP (Interaction to Next Paint)
< 200ms
Interactivity
CLS (Cumulative Layout Shift)
< 0.1
Visual stability
References
Metadata API: See references/metadata-api.md
Sitemap & Robots: See references/sitemap-robots.md
JSON-LD Structured Data: See references/json-ld.md
SEO Audit Checklist: See references/checklist.md
Troubleshooting: See references/troubleshooting.md
Common Mistakes to Avoid
Mixing next-seo with Metadata API - Use only Metadata API in App Router
Missing canonical URLs - Always set alternates.canonical
Using CSR for SEO pages - Use SSG/SSR for indexable content
Blocking /_next/ in robots.txt - Crawlers need render-critical CSS/JS; never disallow /_next/
Missing metadataBase - Required for relative URLs in metadata
Viewport in metadata - Must be a separate export
Mixing metadata object and generateMetadata - Use one or the other, not both
Quick Fixes
Add noindex to a page
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
},
};
Dynamic metadata per page
type Props = { params: Promise<{ id: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params; // params is a Promise in current Next.js
const product = await getProduct(id);
return {
title: product.name,
description: product.description,
};
}
Canonical for dynamic routes
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
return {
alternates: {
canonical: `/products/${slug}`,
},
};
}
26:["$don't have the plugin yet? install it then click "run inline in claude" again.