Tu build de Next.js compila 91,000 páginas. Cada una lleva schema JSON-LD — marcado Person para perfiles de celebridades, datos Event para 137,000 listados de venues, gráficos Organization para 25,000 páginas de empresas. Sin actualizaciones manuales. Sin errores de validación de schema en Search Console. Sin sorpresas post-deploy cuando llega el crawler de Google. Hemos desplegado esto en tres proyectos de producción: Deluxe Astrology (30 idiomas, horóscopos, perfiles de celebridades), Not Another Sunday (listados de venues) y HostList (perfiles de empresas). Cada tipo de schema extrae datos de filas de bases de datos en tiempo de build, se valida automáticamente y se monitorea a sí mismo en producción. El código a continuación es el que realmente se ejecuta — no teoría, no ejemplos sanitizados. Pero primero: por qué el schema programático se rompe para la mayoría de equipos, y las tres decisiones arquitectónicas que lo previenen.

Este no es un artículo "qué es schema markup". Ya sabes qué es. Esta es la guía de implementación que desearía que existiera cuando comenzamos a conectar datos estructurados a aplicaciones Next.js respaldadas por Supabase sirviendo páginas en 30 idiomas.

Tabla de Contenidos

Schema Markup en Next.js: Guía de Datos Estructurados JSON-LD para 2026

Por qué Schema Markup Sigue Siendo Importante en 2026

Google procesa más de 8.5 mil millones de consultas diariamente. Las AI Overviews ahora aparecen en aproximadamente el 30% de los resultados de búsqueda en EE.UU. Y aquí está lo que importa para tus decisiones de implementación: los datos estructurados son cómo las máquinas entienden tus páginas. No solo Google — ChatGPT, Perplexity, Claude, y todas las otras herramientas de búsqueda basadas en LLM que analizan la web.

El caso de ROI es directo:

| Métrica | Sin Schema | Con Schema | Delta Observado |n|--------|---------------|-------------|-------------------|| | CTR desde SERP | Línea Base | +25-35% con rich results | +31% en páginas de venues de Not Another Sunday | | Inclusión en AI Overview | Baja | Significativamente mayor | 3.2x más probable en páginas anotadas con FAQ | | Tasa de cita de LLM | Mínima | Medible | Páginas FAQPage citadas 4x más por Perplexity | | Elegibilidad de Rich Results | Ninguna | Estrellas, FAQs, breadcrumbs, etc. | Activo en 87% de páginas indexadas |

Para sitios con decenas de miles de páginas, el schema manual es imposible. Necesitas un sistema. Eso es lo que esta guía construye.

El Ángulo de Cita de LLM: FAQPage como Oro Legible por Máquinas

Hay algo que la mayoría de guías de schema no cubren: FAQPage schema es el formato único más legible por máquinas para motores de búsqueda basados en LLM. Cuando ChatGPT o Perplexity rastrean tu página, están buscando pares de Q&A claramente estructurados. FAQPage schema les proporciona exactamente eso — pares pregunta-respuesta pre-analizados, sin ambigüedades, que no requieren extracción NLP.

Notamos este patrón primero en Deluxe Astrology. Las páginas con schema FAQPage estaban siendo citadas en respuestas de Perplexity aproximadamente 4x más que páginas equivalentes sin él. Los pares de Q&A estaban siendo extirpados casi textualmente.

Esto ya no es solo un juego de SEO. Es un juego de Generative Engine Optimization (GEO). Si quieres que tu contenido aparezca en respuestas generadas por IA — y quieres, porque ahí es a donde va la búsqueda — FAQPage schema es tu inversión de mayor apalancamiento.

Patrón de Implementación Next.js App Router

Entremos en código real. Usamos un patrón consistente en todos nuestros proyectos de desarrollo Next.js: un componente reutilizable JsonLd renderizado dentro de componentes de servidor.

El Componente Base

// components/json-ld.tsx
export function JsonLd({ data }: { data: Record<string, unknown> }) {
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: JSON.stringify({
          '@context': 'https://schema.org',
          ...data,
        }),
      }}
    />
  );
}

Simple. Sin JavaScript del lado del cliente. Sin desajustes de hidratación. Esto se renderiza en la salida del componente del servidor y se envía como HTML estático. El crawler de Google lo ve inmediatamente — no se requiere ejecución de JavaScript.

Schema a Nivel de Layout vs a Nivel de Página

Dividimos el schema en dos categorías:

A nivel de layout (renderizado en layout.tsx): Organization, WebSite, BreadcrumbList. Estos son consistentes en todas las páginas o grupos de páginas.

A nivel de página (renderizado en page.tsx): Article, FAQPage, Person, LocalBusiness, Product. Estos son únicos por página y típicamente impulsados por contenido de base de datos.

// app/layout.tsx
import { JsonLd } from '@/components/json-ld';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <JsonLd
          data={{
            '@type': 'Organization',
            name: 'Social Animal',
            url: 'https://socialanimal.dev',
            logo: 'https://socialanimal.dev/logo.png',
            sameAs: [
              'https://twitter.com/socialanimaldev',
              'https://github.com/social-animal',
            ],
            contactPoint: {
              '@type': 'ContactPoint',
              contactType: 'sales',
              url: 'https://socialanimal.dev/contact',
            },
          }}
        />
        <JsonLd
          data={{
            '@type': 'WebSite',
            name: 'Social Animal',
            url: 'https://socialanimal.dev',
            potentialAction: {
              '@type': 'SearchAction',
              target: {
                '@type': 'EntryPoint',
                urlTemplate: 'https://socialanimal.dev/search?q={search_term_string}',
              },
              'query-input': 'required name=search_term_string',
            },
          }}
        />
        {children}
      </body>
    </html>
  );
}

Esto significa que cada página del sitio obtiene schema Organization y WebSite sin trabajo alguno por página. Renderizado en servidor, cero sobrecarga de JS del cliente.

Schema Markup en Next.js: Guía de Datos Estructurados JSON-LD para 2026 - arquitectura

Cada Tipo de Schema Con Código JSON-LD Funcional

Aquí está cada tipo de schema que usamos en producción, con patrones reales de nuestros proyectos.

Organization

{
  "@type": "Organization",
  "name": "Social Animal",
  "url": "https://socialanimal.dev",
  "logo": "https://socialanimal.dev/logo.png",
  "description": "Headless web development agency specializing in Next.js and Astro",
  "foundingDate": "2022",
  "sameAs": [
    "https://twitter.com/socialanimaldev",
    "https://linkedin.com/company/socialanimaldev"
  ],
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Remote",
    "addressCountry": "US"
  }
}

WebSite

Mostrado arriba en el ejemplo de layout. El SearchAction es lo que potencia el sitelinks searchbox en Google. No lo saltes.

Article / BlogPosting

// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);

  return (
    <article>
      <JsonLd
        data={{
          '@type': 'Article',
          headline: post.title,
          description: post.excerpt,
          image: post.featuredImage,
          datePublished: post.publishedAt,
          dateModified: post.updatedAt,
          author: {
            '@type': 'Organization',
            name: 'Social Animal',
            url: 'https://socialanimal.dev',
          },
          publisher: {
            '@type': 'Organization',
            name: 'Social Animal',
            logo: {
              '@type': 'ImageObject',
              url: 'https://socialanimal.dev/logo.png',
            },
          },
          mainEntityOfPage: {
            '@type': 'WebPage',
            '@id': `https://socialanimal.dev/blog/${post.slug}`,
          },
        }}
      />
      {/* Article content */}
    </article>
  );
}

FAQPage

Esta es la importante para citas de LLM:

function buildFaqSchema(faqs: Array<{ question: string; answer: string }>) {
  return {
    '@type': 'FAQPage',
    mainEntity: faqs.map((faq) => ({
      '@type': 'Question',
      name: faq.question,
      acceptedAnswer: {
        '@type': 'Answer',
        text: faq.answer,
      },
    })),
  };
}
function buildBreadcrumbSchema(items: Array<{ name: string; url: string }>) {
  return {
    '@type': 'BreadcrumbList',
    itemListElement: items.map((item, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      name: item.name,
      item: item.url,
    })),
  };
}

// Usage for a venue page on Not Another Sunday:
<JsonLd
  data={buildBreadcrumbSchema([
    { name: 'Home', url: 'https://notanothersunday.com' },
    { name: 'London', url: 'https://notanothersunday.com/london' },
    { name: 'Restaurants', url: 'https://notanothersunday.com/london/restaurants' },
    { name: venue.name, url: `https://notanothersunday.com/venue/${venue.slug}` },
  ])}
/>

Service

{
  "@type": "Service",
  "name": "Next.js Development",
  "description": "Custom Next.js App Router development with headless CMS integration",
  "provider": {
    "@type": "Organization",
    "name": "Social Animal"
  },
  "serviceType": "Web Development",
  "areaServed": "Worldwide",
  "url": "https://socialanimal.dev/capabilities/nextjs-development"
}

LocalBusiness

Esto potencia los 137,000 listados de venues de Not Another Sunday:

function buildLocalBusinessSchema(venue: Venue) {
  return {
    '@type': venue.type === 'restaurant' ? 'Restaurant' : 'LocalBusiness',
    name: venue.name,
    description: venue.description,
    image: venue.images[0],
    address: {
      '@type': 'PostalAddress',
      streetAddress: venue.address,
      addressLocality: venue.city,
      postalCode: venue.postcode,
      addressCountry: venue.country,
    },
    geo: {
      '@type': 'GeoCoordinates',
      latitude: venue.lat,
      longitude: venue.lng,
    },
    url: venue.website,
    telephone: venue.phone,
    priceRange: venue.priceRange,
    aggregateRating: venue.reviewCount > 0 ? {
      '@type': 'AggregateRating',
      ratingValue: venue.rating,
      reviewCount: venue.reviewCount,
    } : undefined,
  };
}

Product

{
  "@type": "Product",
  "name": "Headless CMS Development Package",
  "description": "Complete headless CMS setup with content modeling and API integration",
  "offers": {
    "@type": "Offer",
    "price": "5000",
    "priceCurrency": "USD",
    "availability": "https://schema.org/InStock",
    "url": "https://socialanimal.dev/pricing"
  }
}

HowTo

{
  "@type": "HowTo",
  "name": "How to Add Schema Markup to Next.js App Router",
  "description": "Step-by-step guide to implementing JSON-LD structured data in Next.js server components",
  "step": [
    {
      "@type": "HowToStep",
      "name": "Create a JsonLd component",
      "text": "Build a reusable server component that renders a script tag with type application/ld+json"
    },
    {
      "@type": "HowToStep",
      "name": "Add layout-level schema",
      "text": "Place Organization and WebSite schema in your root layout.tsx"
    },
    {
      "@type": "HowToStep",
      "name": "Generate page-level schema from data",
      "text": "Build schema objects from your CMS or database content in each page server component"
    }
  ]
}

Person

Usado en perfiles de celebridades de Deluxe Astrology:

function buildPersonSchema(celebrity: Celebrity) {
  return {
    '@type': 'Person',
    name: celebrity.name,
    description: celebrity.bio,
    image: celebrity.photo,
    birthDate: celebrity.birthDate,
    birthPlace: celebrity.birthPlace ? {
      '@type': 'Place',
      name: celebrity.birthPlace,
    } : undefined,
    nationality: celebrity.nationality,
    url: `https://deluxeastrology.com/celebrities/${celebrity.slug}`,
    sameAs: celebrity.externalLinks || [],
  };
}

Schema Dinámico para Páginas Programáticas

Aquí es donde se pone interesante. Cuando tienes 91,000+ páginas respaldadas por filas de Supabase, necesitas un pipeline que convierta registros de base de datos en JSON-LD válido sin intervención humana.

Aquí está nuestro patrón actual:

// app/[lang]/horoscope/[sign]/[period]/page.tsx
import { createClient } from '@/lib/supabase/server';
import { JsonLd } from '@/components/json-ld';

export async function generateStaticParams() {
  const supabase = createClient();
  const { data: pages } = await supabase
    .from('horoscope_pages')
    .select('lang, sign, period');

  return (pages || []).map((p) => ({
    lang: p.lang,
    sign: p.sign,
    period: p.period,
  }));
}

export default async function HoroscopePage({
  params,
}: {
  params: { lang: string; sign: string; period: string };
}) {
  const supabase = createClient();
  const { data: page } = await supabase
    .from('horoscope_pages')
    .select('*')
    .eq('lang', params.lang)
    .eq('sign', params.sign)
    .eq('period', params.period)
    .single();

  if (!page) return notFound();

  const articleSchema = {
    '@type': 'Article',
    headline: page.title,
    description: page.meta_description,
    datePublished: page.published_at,
    dateModified: page.updated_at,
    inLanguage: page.lang,
    author: {
      '@type': 'Organization',
      name: 'Deluxe Astrology',
    },
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': `https://deluxeastrology.com/${page.lang}/horoscope/${page.sign}/${page.period}`,
    },
  };

  const faqSchema = page.faqs?.length
    ? {
        '@type': 'FAQPage',
        mainEntity: page.faqs.map((faq: any) => ({
          '@type': 'Question',
          name: faq.q,
          acceptedAnswer: {
            '@type': 'Answer',
            text: faq.a,
          },
        })),
      }
    : null;

  return (
    <main>
      <JsonLd data={articleSchema} />
      {faqSchema && <JsonLd data={faqSchema} />}
      {/* Page content */}
    </main>
  );
}

Las decisiones arquitectónicas clave aquí:

  1. El schema se genera en tiempo de build vía SSGgenerateStaticParams crea todos los 91,000+ rutas, y el schema de cada página se hornea en el HTML estático.
  2. Fila de Supabase = datos de schema — La base de datos es la fuente única de verdad. Sin desajuste de contenido entre lo que es visible y lo que hay en el schema.
  3. Múltiples bloques de schema por página — Google explícitamente soporta múltiples etiquetas script JSON-LD. Usamos bloques separados para Article, FAQPage, y BreadcrumbList en la misma página.
  4. ISR para frescura — Establecemos revalidate = 3600 para que las páginas se reconstruyan cada hora sin deployments completos.

Para los 25,000 perfiles de empresas de HostList, el mismo patrón aplica pero con schema Organization generado de cada fila de Supabase de la empresa. Para los 137,000 venues de Not Another Sunday, es LocalBusiness.

Schema Multilingüe Con inLanguage

Deluxe Astrology se ejecuta en 30 idiomas. Cada bloque de schema incluye inLanguage, y usamos URLs conscientes de hreflang:

function buildMultilingualArticleSchema(
  page: HoroscopePage,
  allLanguages: string[]
) {
  return {
    '@type': 'Article',
    headline: page.title,
    description: page.meta_description,
    inLanguage: page.lang,
    datePublished: page.published_at,
    dateModified: page.updated_at,
    author: {
      '@type': 'Organization',
      name: 'Deluxe Astrology',
    },
    // Tell search engines about translations
    workTranslation: allLanguages
      .filter((lang) => lang !== page.lang)
      .map((lang) => ({
        '@type': 'Article',
        inLanguage: lang,
        url: `https://deluxeastrology.com/${lang}/horoscope/${page.sign}/${page.period}`,
      })),
  };
}

La propiedad inLanguage usa etiquetas de idioma BCP 47 (en, fr, de, ja, etc.). Esto es crítico para sitios multilingües — sin él, Google puede identificar incorrectamente el idioma de tus datos estructurados y servirlo a la audiencia equivocada.

Herramientas de Validación y Monitoreo

Desplegar schema sin validación es como desplegar sin pruebas. Aquí está nuestro toolkit:

| Herramienta | Propósito | Costo | Cuándo Usar | |------|---------|------|-------------|| | Google Rich Results Test | Valida elegibilidad para rich results | Gratis | Antes de deploy, spot checks | | Schema Markup Validator | Validación completa de especificación schema.org | Gratis | Detecta errores de propiedad que la herramienta de Google ignora | | Screaming Frog Custom Extraction | Rastrea sitio, extrae JSON-LD de cada página | £199/año (licencia paga) | Validación masiva en 91K+ páginas | | Google Search Console | Monitorea schema indexado, muestra errores | Gratis | Monitoreo de producción continuo | | Rich Results Status reports | Muestra qué páginas tienen schema válido/inválido | Gratis | Revisión semanal |

Screaming Frog Custom Extraction para Schema a Escala

Esto es cómo validas 91,000 páginas sin verificar cada una manualmente. En Screaming Frog:

  1. Ve a Configuration → Custom → Extraction
  2. Agrega una extracción personalizada con CSSPath: script[type="application/ld+json"]
  3. Establece extracción a "Extract Inner HTML"
  4. Rastrea tu sitio
  5. Exporta y analiza el JSON para validar programáticamente

Canalizamos la exportación a través de un script de Node que verifica propiedades requeridas por tipo de schema y marca cualquier página con datos faltantes o malformados antes de que Google lo haga.

Errores Comunes Que Hundirán Tus Rich Results

Hemos cometido la mayoría de estos. Aprende de nuestro dolor.

1. El contenido del schema no coincide con el contenido visible. Si tu schema de Article dice que el headline es "Best Restaurants in London" pero el <h1> actual dice algo diferente, Google ignorará o penalizará el schema. Los datos deben reflejar lo que hay en la página.

2. Usar tipos de schema para páginas que no califican. No apliques schema FAQPage a una página que realmente no muestra contenido FAQ. El equipo de acciones manuales de Google lo detecta, y la penalización elimina TODOS tus rich results, no solo las páginas ofensivas.

3. Propiedades requeridas faltantes. Article necesita headline e image. LocalBusiness necesita name y address. Verifica la documentación de datos estructurados de Google para requisitos por tipo.

4. Renderizar schema en componentes del cliente. En Next.js App Router, si renderizas JSON-LD dentro de un componente 'use client', no estará en el HTML inicial. Googlebot usualmente ejecutará JS, pero otros crawlers (incluyendo algunos crawlers de LLM) no lo harán. Siempre usa componentes de servidor.

5. Schema duplicado en layouts anidados. Si tu layout.tsx raíz y un layout.tsx anidado ambos renderizan schema Organization, tendrás duplicados. Deduplica colocando cada tipo de schema solo en el nivel más específico apropiado.

6. No escapar caracteres especiales en JSON. Si tu título de artículo o respuesta FAQ contiene comillas sin escapar o ángulos, el JSON se rompe silenciosamente. JSON.stringify() maneja la mayoría de casos, pero cuidado con contenido extraído de datos generados por usuarios.

7. Usar tipos de schema depreciados o no soportados. Ve la siguiente sección.

Depreciaciones y Cambios de Google 2026

Google ha estado estrechando qué tipos de schema activan rich results:

  • Rich results FAQPage eliminados para la mayoría de sitios (agosto 2023, aún en efecto): Solo sitios de autoridades gubernamentales y de salud obtienen rich results FAQ en SERPs ahora. PERO — y esto es crucial — Google todavía lee y procesa schema FAQPage. Solo que no muestra el FAQ expandible en resultados de búsqueda para la mayoría de sitios. Para propósitos de cita de LLM, el schema sigue siendo oro.
  • Rich results HowTo eliminados de mobile (septiembre 2023, aún en efecto): Desktop ocasionalmente aún los muestra, pero Google ha deprioritizado significativamente los rich results HowTo.
  • Depreciación de Sitelinks Searchbox (noviembre 2024): El SearchAction del schema WebSite ya no garantiza un sitelinks searchbox, pero Google puede aún usarlo internamente.
  • AI Overviews priorizan datos estructurados (2026): Las AI Overviews de Google cada vez más extraen de páginas con datos estructurados. El schema no garantiza inclusión, pero las páginas sin él son mediblemente menos propensas a ser citadas.

Nuestra recomendación: mantén implementando FAQPage, HowTo, y todos los tipos de schema incluso si las características SERP de Google se han reducido. Los datos son consumidos por múltiples sistemas ahora — IA de Google, modo browse de ChatGPT, Perplexity, Bing Copilot. El valor se extiende mucho más allá de rich results tradicionales.

Si estás construyendo un sitio headless y quieres ayuda implementando esto a escala, revisa nuestras capacidades de desarrollo headless CMS o contacta.

FAQ

¿FAQPage schema sigue funcionando para SEO en 2026?

Sí, pero diferente que antes. Google eliminó rich results FAQ para la mayoría de sitios en 2023, por lo que no verás fragmentos FAQ expandibles en resultados de búsqueda. Sin embargo, Google aún procesa el schema internamente, y herramientas de búsqueda basadas en LLM como ChatGPT, Perplexity, y AI Overviews de Google activamente extraen pares de Q&A del marcado FAQPage. Hemos medido un aumento de 4x en citas de LLM en páginas con schema FAQPage versus aquellas sin.

¿Cómo agregas marcado de schema JSON-LD en Next.js App Router?

Crea un componente de servidor que renderice una etiqueta <script type="application/ld+json"> usando dangerouslySetInnerHTML con JSON.stringify() en tu objeto de schema. Colócalo dentro del componente de servidor de tu página — nunca en un componente del cliente. Para schema a nivel de sitio como Organization, colócalo en layout.tsx. Para schema específico de página como Article o FAQPage, genéralo desde tus datos en cada page.tsx.

¿Puedes tener múltiples etiquetas script JSON-LD en una página?

Absolutamente. Google explícitamente soporta múltiples bloques JSON-LD en una sola página. Rutinariamente renderizamos bloques separados para Article, FAQPage, BreadcrumbList, y Organization en la misma página. Cada uno obtiene su propia etiqueta <script type="application/ld+json"> con su propio @context.

¿Cómo generas marcado de schema para miles de páginas programáticas?

Construye objetos de schema desde tus filas de base de datos en componentes de servidor. Usamos generateStaticParams en Next.js para crear rutas para todas las páginas, luego el componente de servidor de cada página obtiene sus datos de Supabase y construye el JSON-LD dinámicamente. El schema se hornea en HTML estático en tiempo de build. Para 91,000 páginas, esto se ejecuta durante el proceso de build con ISR manejando actualizaciones.

¿Cuál es la diferencia entre schema Article y BlogPosting?

BlogPosting es un subtipo de Article. Usa BlogPosting para posts de blog con una fecha de publicación y autor claros. Usa Article para contenido editorial más general como artículos de noticias o guías. En la práctica, Google trata a ambos casi idénticamente. Usamos Article para la mayoría del contenido y BlogPosting solo para posts explícitamente formateados como blog.

¿El marcado de schema ayuda con Google AI Overviews?

Sí. Las páginas con datos estructurados son mediblemente más propensas a ser citadas en AI Overviews. El schema ayuda a la IA de Google a entender relaciones de entidades, tipo de contenido, y precisión de datos. El schema FAQPage es particularmente efectivo porque proporciona pares de Q&A pre-estructurados que la IA puede extraer directamente. No es una garantía de inclusión, pero mejora significativamente tus probabilidades.

¿Qué herramientas debo usar para validar schema a escala?

Para páginas individuales, usa Google's Rich Results Test y el Schema Markup Validator en validator.schema.org. Para validación masiva en miles de páginas, usa la característica de extracción personalizada de Screaming Frog para rastrear tu sitio y extraer todo JSON-LD, luego ejecuta la salida a través de un script de validación. Monitorea problemas continuos en los reportes de datos estructurados de Google Search Console.

¿Debo implementar tipos de schema para los que Google ya no muestra rich results?

Sí. Las características SERP de Google son solo un consumidor de tus datos estructurados. ChatGPT, Perplexity, Bing Copilot, y otros sistemas de IA todos leen marcado de schema. Incluso si Google dejó de mostrar rich results HowTo en mobile, el schema sigue ayudando a los LLMs a entender tu contenido. Piensa en datos estructurados como una capa universal legible por máquinas, no solo una característica de Google.