seo·8 min de lectura

SEO en Next.js: Metadata API, Open Graph y Sitemap

Optimiza el SEO de tu app Next.js. Metadata API, Open Graph, Twitter Cards, sitemap dinámico, robots.txt y structured data con JSON-LD.

SEO en Next.js: Metadata API, Open Graph y Sitemap

El SEO en Next.js ya no requiere paquetes externos como next-seo. El App Router tiene una Metadata API nativa que genera meta tags, Open Graph, Twitter Cards y más. Combinado con sitemap dinámico y structured data, tienes todo lo que Google necesita.

Metadata API básica

Exporta un objeto metadata desde cualquier page.tsx o layout.tsx:

typescript
// app/page.tsx
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: "Mi App - Inicio",
  description: "Descripción que aparece en Google. 150-160 caracteres ideal.",
  keywords: ["nextjs", "react", "typescript"],
};

Next.js genera automáticamente:

html
<title>Mi App - Inicio</title>
<meta name="description" content="Descripción que aparece en Google..." />
<meta name="keywords" content="nextjs, react, typescript" />

Metadata dinámica (páginas con [slug])

Para páginas dinámicas como posts de blog:

typescript
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
 
export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
 
  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      type: "article",
      publishedTime: post.publishedAt,
      images: [{ url: post.image, width: 1200, height: 630 }],
    },
  };
}

Template de título

Define un template en el layout root para no repetir el nombre del sitio:

typescript
// app/layout.tsx
export const metadata: Metadata = {
  title: {
    default: "Mi App",
    template: "%s | Mi App", // "Sobre Mi | Mi App"
  },
};

Open Graph y Twitter Cards

Controla cómo se ve tu página al compartirla:

typescript
export const metadata: Metadata = {
  openGraph: {
    type: "website",
    title: "Mi App",
    description: "Descripción para redes sociales",
    url: "https://miapp.com",
    siteName: "Mi App",
    locale: "es_ES",
    images: [{
      url: "https://miapp.com/og-image.jpg",
      width: 1200,
      height: 630,
      alt: "Preview de Mi App",
    }],
  },
  twitter: {
    card: "summary_large_image",
    title: "Mi App",
    description: "Descripción para Twitter",
    images: ["https://miapp.com/og-image.jpg"],
  },
};

La imagen OG debe ser de 1200x630 px. Es lo que aparece como preview en Twitter, LinkedIn, WhatsApp y Slack.

Sitemap dinámico

typescript
// app/sitemap.ts
import type { MetadataRoute } from "next";
 
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts(); // tu función para obtener posts
 
  const blogUrls = posts.map((post) => ({
    url: `https://miapp.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: "monthly" as const,
    priority: 0.8,
  }));
 
  return [
    {
      url: "https://miapp.com",
      lastModified: new Date(),
      changeFrequency: "weekly",
      priority: 1,
    },
    {
      url: "https://miapp.com/blog",
      lastModified: new Date(),
      changeFrequency: "weekly",
      priority: 0.9,
    },
    ...blogUrls,
  ];
}

Next.js lo sirve automáticamente en /sitemap.xml. Para más detalles, revisa la guia de sitemap automatico en Next.js.

robots.txt

typescript
// app/robots.ts
import type { MetadataRoute } from "next";
 
export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: "*",
        allow: "/",
        disallow: ["/api/", "/admin/"],
      },
    ],
    sitemap: "https://miapp.com/sitemap.xml",
  };
}

Structured Data (JSON-LD)

Google usa structured data para rich results. Para un blog, BlogPosting y FAQPage son los más útiles:

typescript
// En tu page.tsx
const articleSchema = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  headline: post.title,
  description: post.description,
  datePublished: post.publishedAt,
  dateModified: post.updatedAt,
  author: {
    "@type": "Person",
    name: "Rod Alexanderson",
  },
};
 
return (
  <>
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: JSON.stringify(articleSchema),
      }}
    />
    {/* Tu contenido */}
  </>
);
typescript
const faqSchema = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  mainEntity: faqs.map((faq) => ({
    "@type": "Question",
    name: faq.question,
    acceptedAnswer: {
      "@type": "Answer",
      text: faq.answer,
    },
  })),
};

Las FAQs con structured data tienen más probabilidad de aparecer en "People Also Ask" de Google.

Checklist de SEO

ElementoDondeVerificar
Title únicoCada page.tsx< 60 caracteres
Meta descriptionCada page.tsx150-160 caracteres
Open Graph imagepáginas importantes1200x630px
Canonical URLpáginas con contenido duplicadoalternates.canonical
Sitemap/sitemap.xmlTodas las páginas incluidas
robots.txt/robots.txtNo bloquea páginas importantes
JSON-LDPosts del blogBlogPosting + FAQPage
HeadingsContenidoH1 único, H2/H3 jerarquicos

Siguiente paso

El SEO técnico es la base. Para que tu contenido rankee, necesitas contenido de calidad con buena estructura. Si deployeas en Vercel, la guía de deploy en Vercel cubre la configuración completa incluyendo dominios y analytics.

#seo#nextjs#metadata#opengraph#sitemap#structured-data

Preguntas frecuentes

¿Cómo agrego meta tags en Next.js?

Con la Metadata API. Exportas un objeto metadata o una función generateMetadata desde tu page.tsx o layout.tsx. Next.js genera automáticamente los meta tags en el HTML.

¿Cómo genero un sitemap dinámico en Next.js?

Crea un archivo app/sitemap.ts que exporte una función. Esa función lee tus páginas (de la base de datos, del filesystem, etc.) y devuelve un array con las URLs. Next.js lo sirve como XML en /sitemap.xml automáticamente.

¿Qué es Open Graph y por qué importa?

Open Graph son meta tags que controlan cómo se ve tu página cuando alguien la comparte en redes sociales. Sin ellos, Twitter/LinkedIn/WhatsApp muestran un preview genérico. Con ellos, muestran tu título, descripción e imagen custom.

¿Necesito structured data (JSON-LD) en Next.js?

No es obligatorio, pero ayuda. Google usa structured data para entender mejor tu contenido y mostrarlo en rich results (FAQ, breadcrumbs, artículos). Para un blog, BlogPosting y FAQPage son los schemas más útiles.