GEO con Next.js: Guía completa de implementación
intermediateNext.js App Router implementa GEO via la Metadata API: exporta una función generateMetadata() que retorna title, description, openGraph y articleDates. Añade JSON-LD via un componente Script con type='application/ld+json'. Next.js genera HTML estático en tiempo de build, que los crawlers de IA pueden leer.
GEO con Next.js: Guía completa de implementación
Next.js App Router implementa GEO via la Metadata API: exporta una función generateMetadata() que retorna title, description, openGraph y articleDates. Añade JSON-LD via un componente Script con type="application/ld+json". Next.js genera HTML estático en tiempo de build, que los crawlers de IA pueden leer.
Next.js es adecuado para GEO porque App Router realiza server-side rendering por defecto, produciendo HTML estático que los crawlers de IA pueden procesar sin ejecutar JavaScript.
Implementación de página (App Router)
// app/guia-geo/page.tsx
import type { Metadata } from 'next'
import Script from 'next/script'
export const metadata: Metadata = {
title: 'Cómo implementar GEO en Next.js | Mi Sitio',
description: 'GEO en Next.js requiere JSON-LD, Metadata API y SSR. Guía completa con ejemplos.',
authors: [{ name: 'Mi Empresa', url: 'https://misitio.com/about' }],
alternates: {
canonical: 'https://misitio.com/guia-geo',
},
openGraph: {
type: 'article',
title: 'Cómo implementar GEO en Next.js',
description: 'Guía técnica completa de GEO para Next.js',
url: 'https://misitio.com/guia-geo',
siteName: 'Mi Sitio',
images: [
{
url: 'https://misitio.com/og/guia-geo.jpg',
width: 1200,
height: 630,
},
],
locale: 'es_ES',
publishedTime: '2026-04-18T00:00:00Z',
modifiedTime: '2026-04-18T00:00:00Z',
authors: ['https://misitio.com/author/mi-empresa'],
section: 'Guías técnicas',
tags: ['GEO', 'Next.js', 'Optimización IA'],
},
robots: { index: true, follow: true },
}
export default function Page() {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Cómo implementar GEO en Next.js',
description: 'Guía técnica completa de GEO para Next.js',
author: {
'@type': 'Organization',
name: 'Mi Empresa',
},
publisher: {
'@type': 'Organization',
name: 'Mi Sitio',
logo: {
'@type': 'ImageObject',
url: 'https://misitio.com/logo.png',
},
},
datePublished: '2026-04-18T00:00:00Z',
dateModified: '2026-04-18T00:00:00Z',
mainEntityOfPage: {
'@type': 'WebPage',
'@id': 'https://misitio.com/guia-geo',
},
}
return (
<>
<Script
id="article-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<main>
<article>
<h1>Cómo implementar GEO en Next.js</h1>
{/* Pirámide invertida: respuesta directa primero */}
<p>
GEO en Next.js se implementa via la Metadata API para meta tags y
componentes Script para JSON-LD. App Router provee SSR por defecto,
haciendo las páginas inmediatamente accesibles a los crawlers de IA.
</p>
</article>
</main>
</>
)
}
Metadata dinámica con generateMetadata
Para páginas basadas en contenido (posts de blog, documentación), usa generateMetadata() para generar metadata dinámicamente desde tu fuente de datos:
// 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} | Mi Sitio`,
description: post.excerpt,
alternates: { canonical: `https://misitio.com/blog/${params.slug}` },
openGraph: {
type: 'article',
title: post.title,
description: post.excerpt,
url: `https://misitio.com/blog/${params.slug}`,
publishedTime: post.publishedAt,
modifiedTime: post.updatedAt,
authors: [post.authorUrl],
section: post.category,
tags: post.tags,
},
}
}
Metadata del layout raíz
Establece defaults para todo el sitio en el layout raíz:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://misitio.com'),
title: {
default: 'Mi Sitio',
template: '%s | Mi Sitio',
},
description: 'Descripción por defecto del sitio para páginas sin descripciones específicas.',
openGraph: {
siteName: 'Mi Sitio',
locale: 'es_ES',
},
robots: {
index: true,
follow: true,
},
}
robots.ts (App Router)
Crea /app/robots.ts para generación programática de robots.txt:
// 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://misitio.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://misitio.com',
lastModified: new Date(),
priority: 1,
},
...posts.map(post => ({
url: `https://misitio.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
priority: 0.8,
})),
]
}
Pages Router (legacy)
Si usas Pages Router, usa getStaticProps o getServerSideProps (nunca solo CSR) e inyecta metadata via next/head:
// pages/guia-geo.tsx
import Head from 'next/head'
export default function GuiaGeo({ post }) {
return (
<>
<Head>
<title>{post.title} | Mi Sitio</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://misitio.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>{/* contenido */}</article>
</>
)
}
Requisito: App Router usa SSR por defecto. Con Pages Router usar
getServerSidePropsogetStaticProps. Nunca CSR puro para contenido indexable.
Checklist GEO para Next.js
- App Router con SSR (default) — nunca usar
'use client'en páginas SEO - Metadata API: template de title, description, alternates.canonical
- openGraph con publishedTime y modifiedTime
- JSON-LD via componente Script o dangerouslySetInnerHTML
- robots.ts con los 8 crawlers de IA explícitamente permitidos
- sitemap.ts con fechas lastModified de la fuente de contenido
- metadataBase configurado en el layout raíz
- Estructura de contenido en pirámide invertida en componentes de página
- Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1