Mastering Next.js App Router SEO: Metadata, JSON-LD and Core Web Vitals
Search engines and AI crawlers read your HTML differently than humans do. This guide covers every layer of a production-grade Next.js SEO stack — from the generateMetadata API through JSON-LD structured data to Lighthouse score optimisation.
Why SEO Is Architecture, Not Afterthought
An SEO layer bolted on after launch requires retrofitting semantic HTML, re-writing metadata for hundreds of pages, and hunting down render-blocking resources. When you treat SEO as an architectural concern from day one, every component renders with the right semantics, every page has correct Open Graph tags, and your Lighthouse score stays green through feature additions.
The Next.js Metadata API
Static Metadata
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL("https://yoursite.com"),
title: {
default: "Your Name | Full-Stack Developer",
template: "%s | Your Name",
},
description: "...",
openGraph: { ... },
twitter: { card: "summary_large_image", ... },
};
The template field automatically prefixes page titles — blog posts become "My Article | Your Name" without repeating the site name in every page file.
Dynamic Metadata with generateMetadata
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) return {};
return {
title: post.title,
description: post.description,
openGraph: {
type: "article",
title: post.title,
publishedTime: post.date,
tags: post.tags,
},
alternates: { canonical: `https://yoursite.com/en/blog/${slug}` },
};
}
Canonical URLs and hreflang
Every localised page must declare its canonical URL and list all language alternates. Google uses these to avoid duplicate-content penalties and to serve the right language to each searcher.
alternates: {
canonical: `https://yoursite.com/${lang}`,
languages: { en: ".../en", fr: ".../fr", fa: ".../fa" },
},
JSON-LD Structured Data
Structured data is the primary mechanism by which AI systems — including Google's SGE, ChatGPT Browsing, and Perplexity — extract facts about entities on your page.
Person Schema (for personal portfolios)
{
"@context": "https://schema.org",
"@type": "Person",
"@id": "https://yoursite.com/#person",
"name": "Your Name",
"jobTitle": "Full-Stack Developer",
"knowsAbout": ["React", "Next.js", "Node.js"],
"sameAs": ["https://github.com/...", "https://linkedin.com/in/..."]
}
The @id anchor makes your identity referenceable across multiple pages. Google's Knowledge Graph merges entities with matching @id values.
TechArticle Schema (for blog posts)
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "...",
"author": { "@id": "https://yoursite.com/#person" },
"datePublished": "2026-05-10",
"keywords": ["Next.js", "SEO", "JSON-LD"]
}
Core Web Vitals Optimisation
LCP (Largest Contentful Paint) — target < 2.5 s
- Add
priorityprop to above-the-fold<Image>components - Preconnect to external font/image origins:
<link rel="preconnect" href="https://fonts.googleapis.com"> - Serve images in WebP/AVIF via
next/image(automatic)
CLS (Cumulative Layout Shift) — target < 0.1
- Always declare
widthandheighton<Image>— Next.js uses them to reserve space - Avoid inserting DOM nodes above existing content after hydration
INP (Interaction to Next Paint) — target < 200 ms
- Defer non-critical client components with
dynamic(() => import(...), { ssr: false }) - Move heavy computations off the main thread with
useTransition
AI Discoverability Checklist
- [ ]
Personschema withknowsAboutarray listing specific technologies - [ ]
sameAslinks to GitHub, LinkedIn, professional profiles - [ ]
FAQPageschema answering the questions AI will try to extract - [ ] Semantic heading hierarchy: one
<h1>per page, logical<h2>–<h4>nesting - [ ] Descriptive
<title>and<meta description>— AI uses these for citation snippets - [ ]
robots.txtallowing all crawlers except private routes - [ ] Sitemap with
lastModifiedand hreflang alternates
For a working implementation, see the source code for this portfolio on GitHub.