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 référencement naturel (SEO) et la partageabilité 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 un fichier 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 (Layouts), les pages (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 de 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 exemples et des implémentations, 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 de la même route sont fusionnés superficiellement pour former le résultat final des métadonnées d'une 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.

Remplacer 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 les segments tout en 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ériter 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 Edge Runtime, et Next.js ajoute automatiquement les en-têtes corrects pour les images mises en cache à la périphérie (edge), améliorant ainsi les performances et réduisant les recalculs.

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

app/about/route.js
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',
        }}
      >
        Bonjour le monde !
      </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 Edge Runtime 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 actif. Si vous dépassez cette limite, envisagez de réduire la taille des actifs ou de les récupérer au moment de l'exécution.
  • Seuls les formats de police ttf, otf et woff sont pris en charge. Pour maximiser la vitesse d'analyse des polices, préférez ttf ou otf à 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.',
}