Modern SEO in Next.js (App Router) is not about adding meta tags.
It’s about controlling what HTML is delivered to crawlers, how it’s generated, and which pages are eligible for indexing.
This guide focuses on practical implementation patterns + why they matter.
1. Rendering Strategy Comes First
Why this matters
Rendering strategy determines:
- Whether your content is visible in initial HTML
- How fast your page loads (Core Web Vitals)
- Whether Google treats your page as reliable
👉 In other words, this is the foundation of SEO in Next.js
Recommended strategies
| Page Type | Strategy | Why |
|---|---|---|
| Blog / Marketing | SSG (force-cache) |
Fast + fully indexable |
| Large sites | ISR | Scalable with good freshness |
| Personalized | SSR (no-store) |
Needed for dynamic content |
| Dashboard | CSR | Not meant for indexing |
Example
export default async function Page({ params }) {
const post = await fetch(
`https://api.example.com/posts/${params.slug}`,
{ cache: 'force-cache' }
).then(res => res.json())
return <h1>{post.title}</h1>
}
👉 Wrong choice here = everything else won’t fix SEO
2. Ensure Content Exists in Initial HTML
Why this matters
Search engines primarily index server-rendered HTML.
If your content depends on client-side JavaScript:
- Crawlers may not execute it fully
- Indexing becomes delayed or incomplete
👉 This leads to missing or weak rankings
❌ Avoid
'use client'
useEffect(() => {
fetch('/api/data')
}, [])
✅ Prefer
export default async function Page() {
const data = await fetch('https://api.example.com').then(res => res.json())
return <div>{data.title}</div>
}
Verification
curl -s https://example.com | grep "<h1>"
👉 If it’s not in HTML, it’s not reliably indexable
3. Metadata API: Full Implementation
Why this matters
Metadata controls how your page:
- Appears in search results (title/description)
- Avoids duplication (canonical)
- Looks when shared (Open Graph)
👉 It directly impacts CTR (click-through rate) and index quality
Implementation
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
alternates: {
canonical: `https://example.com/blog/${post.slug}`,
},
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
type: 'article',
},
twitter: {
card: 'summary_large_image',
},
}
}
Key rules
- Always define a canonical URL
- Keep metadata consistent with page content
- Use dynamic metadata for dynamic routes
4. Indexing Control (robots + metadata)
Why this matters
Search engines don’t decide what should be indexed—you do.
Without proper control:
- Staging environments can get indexed
- Duplicate pages can appear
- Sensitive routes may leak
👉 This can damage your SEO trust and rankings
robots.ts
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/admin'],
},
sitemap: 'https://example.com/sitemap.xml',
}
}
Page-level control
const isProd = process.env.NEXT_PUBLIC_SITE_URL === 'https://example.com'
export const metadata = {
robots: {
index: isProd,
follow: isProd,
},
}
👉 Always base this on domain, not environment
5. Sitemap Generation
Why this matters
A sitemap is a signal of intent:
“These are the URLs I want indexed.”
If incorrect:
- Crawlers waste time on irrelevant pages
- Important pages may be ignored
- Indexing becomes inconsistent
Implementation
export default async function sitemap() {
const posts = await getPosts()
return posts
.filter(post => post.published)
.map(post => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
}))
}
Rules
- Include only indexable pages
- Match canonical URLs
- Keep it clean (no duplicates)
6. Structured Data (JSON-LD)
Why this matters
Structured data helps search engines:
- Understand your content better
- Display rich results (e.g., article previews)
👉 This improves visibility and CTR, even if it’s not a direct ranking factor
Implementation
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
image: post.image,
datePublished: post.date,
dateModified: post.updatedAt,
author: {
"@type": "Person",
name: post.author,
},
}
7. Performance: Optimize LCP
Why this matters
Core Web Vitals are ranking signals.
- LCP (Largest Contentful Paint) = how fast main content appears
- Slow LCP = worse rankings
👉 SEO is not just content—it’s performance
Example
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
sizes="100vw"
/>
Additional tips
- Keep above-the-fold content server-rendered
- Avoid blocking scripts
- Don’t delay main content with streaming
8. URL and Canonical Consistency
Why this matters
Search engines treat different URLs as different pages—even if content is identical.
Without consistency:
- Duplicate content issues
- Split ranking signals
- Lower SEO performance
Ensure alignment across:
canonical- sitemap URLs
- internal links
9. Scaling Dynamic Routes (ISR)
Why this matters
Large sites can’t statically generate everything.
ISR allows you to:
- Keep pages fast
- Update content incrementally
👉 This balances performance and freshness
Example
export const revalidate = 60
Static params
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map(post => ({
slug: post.slug,
}))
}
10. Verification Checklist
Why this matters
You can’t trust what you see in the browser—JavaScript modifies it.
👉 SEO depends on raw server response
Check HTML
curl -i https://example.com/page
Confirm:
- Content is present
- Metadata is correct
- Canonical is correct
Tools
- Google Search Console (URL Inspection)
- Rich Results Test
Final Checklist
◽️ Data fetched in Server Components
◽️ Correct rendering strategy selected
◽️ HTML contains full content
◽️ Metadata fully implemented
◽️ Canonical URLs consistent
◽️ Sitemap clean and accurate
◽️ Robots configured correctly
◽️ Structured data added
◽️ LCP optimized
◽️ Verified via raw HTML
Conclusion
Technical SEO in Next.js is about intentional control.
- Control how pages are rendered
- Control what gets indexed
- Keep all signals consistent
If you get these right, SEO becomes predictable—not guesswork.
Top comments (0)