Next.js App Router ships a first-class Metadata API that replaces the old next/head pattern. In 2026, a well-configured Next.js app can achieve a technical SEO score of 100 without any additional plugins. This guide covers every metadata lever available, with copy-paste code examples.
The Metadata API
Every page.tsx and layout.tsx can export a metadata object or a generateMetadata() function:
// Static metadata
export const metadata = { title: 'My Page', description: '...' };
// Dynamic metadata
export async function generateMetadata({ params }) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; }
Next.js merges metadata from layouts and pages, with page metadata taking precedence. The root layout.tsx is the right place for site-wide defaults.
Title templates
Use title.template in the root layout to automatically append the site name:
export const metadata = { title: { default: 'Picovert', template: '%s | Picovert' } };
Child pages then only need to set title: 'Convert PNG to WebP' and it renders as Convert PNG to WebP | Picovert.
Open Graph and Twitter Card
Social previews require Open Graph meta tags. Next.js generates them from the openGraph and twitter fields:
openGraph: { title, description, url, siteName: 'Picovert', images: [{ url: '/og/page.png', width: 1200, height: 630 }], type: 'website' }, twitter: { card: 'summary_large_image', title, description, images: ['/og/page.png'] }
OG images should be exactly 1200×630 pixels. Serve them as WebP from /public/og/ for the best load time in link previews.
hreflang for multilingual sites
If your site supports multiple languages, hreflang tags tell Google which URL to show for each locale. In Next.js, use the alternates field:
alternates: { canonical: 'https://picovert.com/blog/slug', languages: { 'x-default': 'https://picovert.com/blog/slug', 'en': 'https://picovert.com/blog/slug', 'ko': 'https://picovert.com/ko/blog/slug', 'ja': 'https://picovert.com/ja/blog/slug' } }
Always include x-default pointing to your default language URL.
JSON-LD structured data
Structured data enables rich results (star ratings, FAQs, breadcrumbs) in Google Search. Add a <script type='application/ld+json'> tag in your page's <head>. In Next.js, inject it via the generateMetadata() return or directly in the JSX:
const jsonLd = { '@context': 'https://schema.org', '@type': 'BlogPosting', headline: post.title, datePublished: post.date, author: { '@type': 'Organization', name: 'Picovert' } };
Use Google's Rich Results Test to verify your structured data is parsed correctly.
Sitemaps
Next.js generates XML sitemaps from a sitemap.ts file in the app directory:
export default function sitemap() { return posts.map(post => ({ url: `https://picovert.com/blog/${post.slug}`, lastModified: post.date, changeFrequency: 'monthly', priority: 0.8 })); }
Submit the sitemap URL in Google Search Console after deployment.
robots.txt
Create a robots.ts file in the app directory:
export default function robots() { return { rules: { userAgent: '*', allow: '/', disallow: '/api/' }, sitemap: 'https://picovert.com/sitemap.xml' }; }
Canonical URLs
Always set a canonical URL to prevent duplicate content penalties from paginated pages, URL parameters, or locale variants that shouldn't be indexed separately:
alternates: { canonical: 'https://picovert.com/blog/my-post' }
Core Web Vitals and SEO
Beyond metadata, Google uses LCP, INP, and CLS as ranking signals. A perfect metadata setup paired with poor Core Web Vitals scores will still hurt your rankings. Optimize images (convert to WebP, add width/height, preload LCP), minimize JavaScript, and eliminate layout shifts — the full checklist is in the companion performance post.
Checklist
- Unique title and description on every page
- Title under 60 characters, description under 155 characters
- Open Graph image at 1200×630
- hreflang for all language variants
- JSON-LD structured data for blog posts, products, FAQs
- Sitemap submitted to Search Console
- Canonical URL on every indexable page
- robots.txt allowing crawlers, disallowing private paths
- LCP under 2.5 s, INP under 200 ms, CLS under 0.1