In the Next.js, SEO is built around Server Components and the Metadata API. This architecture ensures that your content is visible to search engines by default.
Here is the checklist of what you should implement for production-ready SEO.
1. Data Fetching in Server Components
By default, components in the app directory are Server Components. Fetching data here ensures that the final HTML sent to the browser (and crawlers) contains your content immediately.
// app/blog/[slug]/page.tsx
export default async function Page({ params }) {
// Data is fetched on the server and rendered into HTML
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json())
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
2. The Metadata API
Use the built-in Metadata API to manage <title>, <meta>, and Open Graph tags.
Static Metadata
For pages with fixed content (e.g., Home, About).
// app/layout.tsx or app/page.tsx
export const metadata = {
title: 'My App',
description: 'The best app for SEO efficiency.',
}
Dynamic Metadata
For dynamic routes like products or blog posts, use generateMetadata.
// app/products/[id]/page.tsx
export async function generateMetadata({ params }) {
const product = await getProduct(params.id)
return {
title: product.name,
description: product.description,
alternates: {
canonical: `https://example.com/products/${params.id}`,
},
}
}
3. Automated sitemap.xml
Instead of manually maintaining a XML file, use sitemap.ts to generate it dynamically.
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
const postEntries = posts.map((post) => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
}))
return [
{ url: 'https://example.com', lastModified: new Date() },
...postEntries,
]
}
4. Robots Configuration
Use robots.ts to define crawling rules.
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/admin/',
},
sitemap: 'https://example.com/sitemap.xml',
}
}
5. Structured Data (JSON-LD)
Structured data helps search engines provide rich snippets. Inject it as a script tag inside your Server Component.
export default function Page({ post }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
image: post.image,
datePublished: post.date,
}
return (
<section>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<h1>{post.title}</h1>
</section>
)
}
6. Image Optimization with priority
Large Contentful Paint (LCP) is a key SEO ranking factor. Use the priority property for your "Above the Fold" images (like Hero images) to tell Next.js to preload them.
import Image from 'next/image'
export default function Hero() {
return (
<Image
src="/hero-banner.jpg"
alt="Product Hero"
width={1200}
height={600}
priority // Critical for LCP performance
/>
)
}
7. Environment-based Indexing
To prevent your staging or development sites from appearing in search results, toggle the robots metadata based on the environment.
export const metadata = {
robots: {
index: process.env.NODE_ENV === 'production',
follow: process.env.NODE_ENV === 'production',
},
}
8. Verification
After deploying, always verify the raw HTML output. If you can see your keywords and metadata here, the crawlers can too.
curl -i https://your-domain.com/blog/my-post
Summary Checklist
- Server Components: Keep data fetching on the server.
- Metadata API: Use it for titles, descriptions, and OG tags.
- File-based Metadata: Use sitemap.ts and robots.ts.
- JSON-LD: Add structured data for rich results.
- Image Priority: Use priority for LCP images.
Next.js App Router makes SEO highly efficient if you follow these server-first patterns. Focus on the initial HTML, and the rankings will follow.
Top comments (0)