GEO for Next.js: Complete Implementation Guide
intermediateNext.js App Router implements GEO via the Metadata API: export a generateMetadata() function that returns title, description, openGraph, and articleDates. Add JSON-LD via a Script component with type='application/ld+json'. Next.js generates static HTML at build time, which AI crawlers can read.
GEO for Next.js: Complete Implementation Guide
Next.js App Router implements GEO via the Metadata API: export a generateMetadata() function that returns title, description, openGraph, and articleDates. Add JSON-LD via a Script component with type="application/ld+json". Next.js generates static HTML at build time, which AI crawlers can read.
Next.js is well-suited for GEO because App Router performs server-side rendering by default, producing static HTML that AI crawlers can process without executing JavaScript.
Page Implementation (App Router)
// app/geo-guide/page.tsx
import type { Metadata } from 'next'
import Script from 'next/script'
export const metadata: Metadata = {
title: 'How to Implement GEO in Next.js | My Site',
description: 'GEO in Next.js requires JSON-LD, Metadata API, and SSR. Complete guide with examples.',
authors: [{ name: 'My Company', url: 'https://yoursite.com/about' }],
alternates: {
canonical: 'https://yoursite.com/geo-guide',
},
openGraph: {
type: 'article',
title: 'How to Implement GEO in Next.js',
description: 'Complete technical GEO guide for Next.js',
url: 'https://yoursite.com/geo-guide',
siteName: 'My Site',
images: [
{
url: 'https://yoursite.com/og/geo-guide.jpg',
width: 1200,
height: 630,
},
],
locale: 'en_US',
publishedTime: '2026-04-18T00:00:00Z',
modifiedTime: '2026-04-18T00:00:00Z',
authors: ['https://yoursite.com/author/my-company'],
section: 'Technical Guides',
tags: ['GEO', 'Next.js', 'AI Optimization'],
},
robots: { index: true, follow: true },
}
export default function Page() {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'How to Implement GEO in Next.js',
description: 'Complete technical GEO guide for Next.js',
author: {
'@type': 'Organization',
name: 'My Company',
},
publisher: {
'@type': 'Organization',
name: 'My Site',
logo: {
'@type': 'ImageObject',
url: 'https://yoursite.com/logo.png',
},
},
datePublished: '2026-04-18T00:00:00Z',
dateModified: '2026-04-18T00:00:00Z',
mainEntityOfPage: {
'@type': 'WebPage',
'@id': 'https://yoursite.com/geo-guide',
},
}
return (
<>
<Script
id="article-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<main>
<article>
<h1>How to Implement GEO in Next.js</h1>
{/* Inverted pyramid: direct answer first */}
<p>
GEO in Next.js is implemented via the Metadata API for meta tags and
Script components for JSON-LD. App Router provides SSR by default,
making pages immediately accessible to AI crawlers.
</p>
</article>
</main>
</>
)
}
Dynamic Metadata with generateMetadata
For content-driven pages (blog posts, documentation), use generateMetadata() to generate metadata dynamically from your data source:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
interface Props {
params: { slug: string }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: `${post.title} | My Site`,
description: post.excerpt,
alternates: { canonical: `https://yoursite.com/blog/${params.slug}` },
openGraph: {
type: 'article',
title: post.title,
description: post.excerpt,
url: `https://yoursite.com/blog/${params.slug}`,
publishedTime: post.publishedAt,
modifiedTime: post.updatedAt,
authors: [post.authorUrl],
section: post.category,
tags: post.tags,
},
}
}
export default async function BlogPost({ params }: Props) {
const post = await getPost(params.slug)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.excerpt,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
'@type': 'Person',
name: post.author.name,
url: post.author.url,
},
publisher: {
'@type': 'Organization',
name: 'My Site',
logo: { '@type': 'ImageObject', url: 'https://yoursite.com/logo.png' },
},
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article dangerouslySetInnerHTML={{ __html: post.content }} />
</>
)
}
Root Layout Metadata
Set site-wide defaults in the root layout:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://yoursite.com'),
title: {
default: 'My Site',
template: '%s | My Site',
},
description: 'Default site description for pages without specific descriptions.',
openGraph: {
siteName: 'My Site',
locale: 'en_US',
},
robots: {
index: true,
follow: true,
},
}
robots.txt (App Router)
Create /app/robots.ts for programmatic robots.txt generation:
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: 'GPTBot', allow: '/' },
{ userAgent: 'OAI-SearchBot', allow: '/' },
{ userAgent: 'ClaudeBot', allow: '/' },
{ userAgent: 'Claude-User', allow: '/' },
{ userAgent: 'Claude-SearchBot', allow: '/' },
{ userAgent: 'PerplexityBot', allow: '/' },
{ userAgent: 'Google-Extended', allow: '/' },
{ userAgent: 'BingBot', allow: '/' },
{ userAgent: '*', allow: '/' },
],
sitemap: 'https://yoursite.com/sitemap.xml',
}
}
sitemap.ts
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
return [
{
url: 'https://yoursite.com',
lastModified: new Date(),
priority: 1,
},
...posts.map(post => ({
url: `https://yoursite.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
priority: 0.8,
})),
]
}
Pages Router (Legacy)
If using Pages Router, use getStaticProps or getServerSideProps (never CSR only) and inject metadata via next/head:
// pages/geo-guide.tsx
import Head from 'next/head'
export default function GeoGuide({ post }) {
return (
<>
<Head>
<title>{post.title} | My Site</title>
<meta name="description" content={post.excerpt} />
<meta property="og:type" content="article" />
<meta property="article:published_time" content={post.publishedAt} />
<meta property="article:modified_time" content={post.updatedAt} />
<link rel="canonical" href={`https://yoursite.com/${post.slug}`} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
})
}}
/>
</Head>
<article>{/* content */}</article>
</>
)
}
GEO Checklist for Next.js
- App Router with SSR (default) — never use
'use client'on SEO pages - Metadata API: title template, description, alternates.canonical
- openGraph with publishedTime and modifiedTime
- JSON-LD via Script component or dangerouslySetInnerHTML
- robots.ts with all 8 AI crawlers explicitly allowed
- sitemap.ts with lastModified dates from content source
- metadataBase set in root layout
- Inverted pyramid content structure in page components
- Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1