Métadonnées

Next.js propose une API Metadata qui permet de définir les métadonnées de votre application (comme les balises meta et link à l'intérieur de l'élément HTML head) pour améliorer le SEO et le partage sur le web.

Il existe deux façons d'ajouter des métadonnées à votre application :

  • Métadonnées basées sur la configuration : Exportez un objet metadata statique ou une fonction dynamique generateMetadata dans un fichier layout.js ou page.js.
  • Métadonnées basées sur les fichiers : Ajoutez des fichiers spéciaux statiques ou générés dynamiquement aux segments de route.

Avec ces deux options, Next.js générera automatiquement les éléments <head> pertinents pour vos pages. Vous pouvez également créer des images OG dynamiques en utilisant le constructeur ImageResponse.

Métadonnées statiques

Pour définir des métadonnées statiques, exportez un objet Metadata depuis un fichier layout.js ou page.js statique.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}
export const metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

Pour toutes les options disponibles, consultez la Référence API.

Métadonnées dynamiques

Vous pouvez utiliser la fonction generateMetadata pour récupérer (fetch) des métadonnées nécessitant des valeurs dynamiques.

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> {
  // lire les paramètres de route
  const id = params.id

  // récupérer les données
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // accéder et étendre (plutôt que remplacer) les métadonnées parentes
  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) {
  // lire les paramètres de route
  const id = params.id

  // récupérer les données
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // accéder et étendre (plutôt que remplacer) les métadonnées parentes
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }) {}

Pour tous les paramètres disponibles, consultez la Référence API.

Bon à savoir :

  • Les métadonnées statiques et dynamiques via generateMetadata sont uniquement prises en charge dans les composants serveur.
  • Les requêtes fetch sont automatiquement mémoïsées pour les mêmes données dans generateMetadata, generateStaticParams, les mises en page, les pages et les composants serveur. Le cache de React peut être utilisé si fetch n'est pas disponible.
  • Next.js attendra que la récupération des données dans generateMetadata soit terminée avant de diffuser l'interface utilisateur au client. Cela garantit que la première partie d'une réponse en streaming inclut les balises <head>.

Métadonnées basées sur les fichiers

Ces fichiers spéciaux sont disponibles pour les métadonnées :

Vous pouvez les utiliser pour des métadonnées statiques ou générer ces fichiers dynamiquement avec du code.

Pour des implémentations et exemples, consultez la Référence API des fichiers de métadonnées et la Génération d'images dynamiques.

Comportement

Les métadonnées basées sur les fichiers ont une priorité plus élevée et remplaceront toutes les métadonnées basées sur la configuration.

Champs par défaut

Il y a deux balises meta par défaut qui sont toujours ajoutées même si une route ne définit pas de métadonnées :

  • La balise meta charset définit l'encodage des caractères du site.
  • La balise meta viewport définit la largeur et l'échelle de la fenêtre d'affichage pour s'adapter à différents appareils.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

Bon à savoir : Vous pouvez remplacer la balise meta viewport par défaut.

Ordre d'évaluation

Les métadonnées sont évaluées dans l'ordre, en commençant par le segment racine jusqu'au segment le plus proche du fichier page.js final. Par exemple :

  1. app/layout.tsx (Mise en page racine)
  2. app/blog/layout.tsx (Mise en page imbriquée du blog)
  3. app/blog/[slug]/page.tsx (Page du blog)

Fusion

En suivant l'ordre d'évaluation, les objets Metadata exportés depuis plusieurs segments d'une même route sont fusionnés de manière superficielle pour former les métadonnées finales de la route. Les clés en double sont remplacées en fonction de leur ordre.

Cela signifie que les métadonnées avec des champs imbriqués comme openGraph et robots définis dans un segment précédent sont écrasés par le dernier segment qui les définit.

Remplacement des champs

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme est un...',
  },
}
app/blog/page.js
export const metadata = {
  title: 'Blog',
  openGraph: {
    title: 'Blog',
  },
}

// Résultat :
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

Dans l'exemple ci-dessus :

  • Le title de app/layout.js est remplacé par le title dans app/blog/page.js.
  • Tous les champs openGraph de app/layout.js sont remplacés dans app/blog/page.js car app/blog/page.js définit les métadonnées openGraph. Notez l'absence de openGraph.description.

Si vous souhaitez partager certains champs imbriqués entre segments tout en remplaçant d'autres, vous pouvez les extraire dans une variable séparée :

app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }
app/page.js
import { openGraphImage } from './shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Accueil',
  },
}
app/about/page.js
import { openGraphImage } from '../shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'À propos',
  },
}

Dans l'exemple ci-dessus, l'image OG est partagée entre app/layout.js et app/about/page.js tandis que les titres sont différents.

Héritage des champs

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme est un...',
  },
}
app/about/page.js
export const metadata = {
  title: 'À propos',
}

// Résultat :
// <title>À propos</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme est un..." />

Notes

  • Le title de app/layout.js est remplacé par le title dans app/about/page.js.
  • Tous les champs openGraph de app/layout.js sont hérités dans app/about/page.js car app/about/page.js ne définit pas de métadonnées openGraph.

Génération d'images dynamiques

Le constructeur ImageResponse permet de générer des images dynamiques en utilisant JSX et CSS. C'est utile pour créer des images pour les réseaux sociaux comme les images Open Graph, les cartes Twitter, etc.

ImageResponse utilise le Runtime Edge, et Next.js ajoute automatiquement les en-têtes corrects pour les images mises en cache au niveau du edge, améliorant ainsi les performances et réduisant les recalculs.

Pour l'utiliser, vous pouvez importer ImageResponse depuis next/server :

app/about/route.js
import { ImageResponse } from 'next/server'

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',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  )
}

ImageResponse s'intègre bien avec d'autres API Next.js, y compris les Route Handlers et les métadonnées basées sur les fichiers. Par exemple, vous pouvez utiliser ImageResponse dans un fichier opengraph-image.tsx pour générer des images Open Graph au moment de la construction ou dynamiquement au moment de la requête.

ImageResponse prend en charge les propriétés CSS courantes, y compris flexbox et le positionnement absolu, les polices personnalisées, le retour à la ligne du texte, le centrage et les images imbriquées. Voir la liste complète des propriétés CSS prises en charge.

Bon à savoir :

  • Des exemples sont disponibles dans le Vercel OG Playground.
  • ImageResponse utilise @vercel/og, Satori et Resvg pour convertir HTML et CSS en PNG.
  • Seul le Runtime Edge est pris en charge. Le Runtime Node.js par défaut ne fonctionnera pas.
  • Seuls flexbox et un sous-ensemble de propriétés CSS sont pris en charge. Les mises en page avancées (par exemple display: grid) ne fonctionneront pas.
  • Taille maximale du bundle de 500KB. La taille du bundle inclut votre JSX, CSS, polices, images et tout autre asset. Si vous dépassez la limite, envisagez de réduire la taille des assets ou de les récupérer au runtime.
  • Seuls les formats de police ttf, otf et woff sont pris en charge. Pour maximiser la vitesse d'analyse des polices, ttf ou otf sont préférés à woff.

JSON-LD

JSON-LD est un format pour les données structurées qui peut être utilisé par les moteurs de recherche pour comprendre votre contenu. Par exemple, vous pouvez l'utiliser pour décrire une personne, un événement, une organisation, un film, un livre, une recette et bien d'autres types d'entités.

Notre recommandation actuelle pour JSON-LD est de rendre les données structurées sous forme de balise <script> dans vos composants layout.js ou page.js. Par exemple :

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>
      {/* Ajoutez JSON-LD à votre page */}
      <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>
      {/* Ajoutez JSON-LD à votre page */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

Vous pouvez valider et tester vos données structurées avec le Rich Results Test de Google ou le validateur générique Schema Markup Validator.

Vous pouvez typer votre JSON-LD avec TypeScript en utilisant des packages communautaires comme schema-dts :

import { Product, WithContext } from 'schema-dts'

const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Autocollant Next.js',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamique à la vitesse du statique.',
}