Metadatos
Next.js tiene una API de Metadatos que puede utilizarse para definir los metadatos de tu aplicación (por ejemplo, etiquetas meta
y link
dentro del elemento head
de tu HTML) para mejorar el SEO y la capacidad de compartir en la web.
Hay dos formas de agregar metadatos a tu aplicación:
- Metadatos basados en configuración: Exporta un objeto estático
metadata
o una función dinámicagenerateMetadata
en un archivolayout.js
opage.js
. - Metadatos basados en archivos: Agrega archivos especiales estáticos o generados dinámicamente a segmentos de ruta.
Con ambas opciones, Next.js generará automáticamente los elementos <head>
relevantes para tus páginas. También puedes crear imágenes OG dinámicas usando el constructor ImageResponse
.
Metadatos estáticos
Para definir metadatos estáticos, exporta un objeto Metadata
desde un archivo layout.js
o page.js
estático.
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
export const metadata = {
title: '...',
description: '...',
}
export default function Page() {}
Para todas las opciones disponibles, consulta la Referencia de API.
Metadatos dinámicos
Puedes usar la función generateMetadata
para obtener metadatos que requieran valores dinámicos.
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: { id: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// leer parámetros de ruta
const id = params.id
// obtener datos
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// opcionalmente acceder y extender (en lugar de reemplazar) metadatos padre
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}
export async function generateMetadata({ params, searchParams }, parent) {
// leer parámetros de ruta
const id = params.id
// obtener datos
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// opcionalmente acceder y extender (en lugar de reemplazar) metadatos padre
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }) {}
Para todos los parámetros disponibles, consulta la Referencia de API.
Nota importante:
- Tanto los metadatos estáticos como los dinámicos a través de
generateMetadata
solo son compatibles en Componentes de Servidor.- Las solicitudes
fetch
se memorizan automáticamente para los mismos datos engenerateMetadata
,generateStaticParams
, Layouts, Páginas y Componentes de Servidor. Se puede usarcache
de React sifetch
no está disponible.- Next.js esperará a que se complete la obtención de datos dentro de
generateMetadata
antes de transmitir la interfaz de usuario al cliente. Esto garantiza que la primera parte de una respuesta transmitida incluya las etiquetas<head>
.
Metadatos basados en archivos
Estos archivos especiales están disponibles para metadatos:
- favicon.ico, apple-icon.jpg e icon.jpg
- opengraph-image.jpg y twitter-image.jpg
- robots.txt
- sitemap.xml
Puedes usarlos para metadatos estáticos o generar estos archivos programáticamente con código.
Para implementación y ejemplos, consulta la Referencia de API de Archivos de Metadatos y Generación Dinámica de Imágenes.
Comportamiento
Los metadatos basados en archivos tienen mayor prioridad y anularán cualquier metadato basado en configuración.
Campos predeterminados
Hay dos etiquetas meta
predeterminadas que siempre se agregan, incluso si una ruta no define metadatos:
- La etiqueta meta charset establece la codificación de caracteres del sitio web.
- La etiqueta meta viewport establece el ancho y la escala del viewport para que el sitio web se ajuste a diferentes dispositivos.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Nota importante: Puedes sobrescribir la etiqueta meta
viewport
predeterminada.
Orden
Los metadatos se evalúan en orden, comenzando desde el segmento raíz hasta el segmento más cercano al segmento final page.js
. Por ejemplo:
app/layout.tsx
(Layout raíz)app/blog/layout.tsx
(Layout anidado de blog)app/blog/[slug]/page.tsx
(Página de blog)
Fusión
Siguiendo el orden de evaluación, los objetos Metadata exportados desde múltiples segmentos en la misma ruta se fusionan superficialmente para formar la salida final de metadatos de una ruta. Las claves duplicadas se reemplazan según su orden.
Esto significa que los metadatos con campos anidados como openGraph
y robots
que se definen en un segmento anterior son sobrescritos por el último segmento que los define.
Sobrescribir campos
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme es...',
},
}
export const metadata = {
title: 'Blog',
openGraph: {
title: 'Blog',
},
}
// Salida:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />
En el ejemplo anterior:
title
deapp/layout.js
es reemplazado portitle
enapp/blog/page.js
.- Todos los campos
openGraph
deapp/layout.js
son reemplazados enapp/blog/page.js
porqueapp/blog/page.js
establece metadatosopenGraph
. Nota la ausencia deopenGraph.description
.
Si deseas compartir algunos campos anidados entre segmentos mientras sobrescribes otros, puedes extraerlos en una variable separada:
export const openGraphImage = { images: ['http://...'] }
import { openGraphImage } from './shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Inicio',
},
}
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Acerca de',
},
}
En el ejemplo anterior, la imagen OG se comparte entre app/layout.js
y app/about/page.js
mientras que los títulos son diferentes.
Heredar campos
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme es...',
},
}
export const metadata = {
title: 'Acerca de',
}
// Salida:
// <title>Acerca de</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme es..." />
Notas
title
deapp/layout.js
es reemplazado portitle
enapp/about/page.js
.- Todos los campos
openGraph
deapp/layout.js
son heredados enapp/about/page.js
porqueapp/about/page.js
no establece metadatosopenGraph
.
Generación Dinámica de Imágenes
El constructor ImageResponse
te permite generar imágenes dinámicas usando JSX y CSS. Esto es útil para crear imágenes de redes sociales como imágenes Open Graph, tarjetas de Twitter y más.
ImageResponse
usa el Edge Runtime, y Next.js agrega automáticamente los encabezados correctos para imágenes en caché en el edge, lo que ayuda a mejorar el rendimiento y reduce la recomputación.
Para usarlo, puedes importar ImageResponse
desde next/og
:
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export async function GET() {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
}}
>
¡Hola mundo!
</div>
),
{
width: 1200,
height: 600,
}
)
}
ImageResponse
se integra bien con otras API de Next.js, incluyendo Route Handlers y metadatos basados en archivos. Por ejemplo, puedes usar ImageResponse
en un archivo opengraph-image.tsx
para generar imágenes Open Graph en tiempo de compilación o dinámicamente en tiempo de solicitud.
ImageResponse
admite propiedades CSS comunes incluyendo flexbox y posicionamiento absoluto, fuentes personalizadas, ajuste de texto, centrado e imágenes anidadas. Consulta la lista completa de propiedades CSS admitidas.
Nota importante:
- Hay ejemplos disponibles en el Vercel OG Playground.
ImageResponse
usa @vercel/og, Satori y Resvg para convertir HTML y CSS en PNG.- Solo se admite el Edge Runtime. El runtime predeterminado de Node.js no funcionará.
- Solo se admiten flexbox y un subconjunto de propiedades CSS. Los diseños avanzados (por ejemplo,
display: grid
) no funcionarán.- Tamaño máximo de paquete de
500KB
. El tamaño del paquete incluye tu JSX, CSS, fuentes, imágenes y cualquier otro recurso. Si excedes el límite, considera reducir el tamaño de los recursos u obtenerlos en tiempo de ejecución.- Solo se admiten formatos de fuente
ttf
,otf
ywoff
. Para maximizar la velocidad de análisis de fuentes, se prefierenttf
uotf
sobrewoff
.
JSON-LD
JSON-LD es un formato para datos estructurados que los motores de búsqueda pueden usar para entender tu contenido. Por ejemplo, puedes usarlo para describir una persona, un evento, una organización, una película, un libro, una receta y muchos otros tipos de entidades.
Nuestra recomendación actual para JSON-LD es renderizar datos estructurados como una etiqueta <script>
en tus componentes layout.js
o page.js
. Por ejemplo:
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Agrega JSON-LD a tu página */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Agrega JSON-LD a tu página */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
Puedes validar y probar tus datos estructurados con la Prueba de Resultados Enriquecidos de Google o el Validador de Marcado Schema genérico.
Puedes tipar tu JSON-LD con TypeScript usando paquetes de la comunidad como schema-dts
:
import { Product, WithContext } from 'schema-dts'
const jsonLd: WithContext<Product> = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Next.js Sticker',
image: 'https://nextjs.org/imgs/sticker.png',
description: 'Dynamic at the speed of static.',
}