Logo
Booster votre SEO avec Next.js : Guide pratique

Next.js fournit tous les blocs nécessaires à un excellent SEO : SSR/SSG pour la crawlabilité, Metadata API pour des aperçus riches, et outillage performance pour réussir les Core Web Vitals. Dans cet article, on met en place l’essentiel et on évite les pièges courants.


Pourquoi Next.js pour le SEO ?

  • HTML pré‑rendu (SSG/SSR) → meilleure exploration et premier rendu plus rapide
  • App Router & Metadata API → titres propres, URLs canoniques, Open Graph/Twitter cards
  • Optimisation d’images intégrée → LCP plus rapide avec images responsives et lazy‑load
  • Edge & stratégie de cache → garder les pages fraîches sans sacrifier la vitesse

1) Metadata API : titres, canoniques et aperçus

Définissez des valeurs par défaut dans app/layout.tsx, puis surchargez par page. Les canoniques préviennent les contenus dupliqués ; Open Graph/Twitter génèrent des aperçus de liens de qualité.

// app/layout.tsx
import type { Metadata } from "next";

const baseUrl = "https://example.com";

export const metadata: Metadata = {
  metadataBase: new URL(baseUrl),
  title: {
    default: "Example — Construisez vite avec Next.js",
    template: "%s · Example",
  },
  description: "Site Next.js orienté performance et SEO.",
  alternates: {
    canonical: "/",
  },
  openGraph: {
    type: "website",
    url: baseUrl,
    title: "Example — Construisez vite avec Next.js",
    description: "Starter Next.js performance‑first.",
    images: ["/og-default.png"],
  },
  twitter: {
    card: "summary_large_image",
    creator: "@example",
  },
};

Surcharges par page avec generateMetadata :

// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
import { getPostBySlug } from "@/lib/posts";

export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise<Metadata> {
  const post = await getPostBySlug(params.slug);

  const title = post.seoTitle ?? post.title;
  const url = `/blog/${post.slug}`;
  const image = post.coverImage ?? "/og-default.png";

  return {
    title,
    description: post.description,
    alternates: { canonical: url },
    openGraph: {
      type: "article",
      url,
      title,
      description: post.description,
      publishedTime: post.date,
      authors: post.author ? [post.author] : undefined,
      images: [image],
    },
    twitter: {
      card: "summary_large_image",
      title,
      description: post.description,
      images: [image],
    },
    robots: { index: true, follow: true },
  };
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);
  return <article className="prose">{/* Contenu MDX... */}</article>;
}

Astuce : Pour les listes paginées/filtrées, mettez la canonique sur la page de base et, si vous avez une pagination profonde, gérez rel="next" / rel="prev" via <link> dans generateMetadata.


2) Robots.txt & sitemap sans plugins

Générez les deux avec les conventions natives.

// app/robots.ts
import type { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      { userAgent: "*", allow: "/" },
      // Interdire des chemins privés :
      { userAgent: "*", disallow: ["/api/", "/admin/"] },
    ],
    sitemap: "https://example.com/sitemap.xml",
  };
}
// app/sitemap.ts
import type { MetadataRoute } from "next";
import { getAllPostSlugs } from "@/lib/posts";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = "https://example.com";
  const slugs = await getAllPostSlugs();

  const posts = slugs.map((slug) => ({
    url: `${baseUrl}/blog/${slug}`,
    lastModified: new Date(),
    changeFrequency: "weekly",
    priority: 0.7,
  }));

  return [
    { url: `${baseUrl}/`, lastModified: new Date(), changeFrequency: "weekly", priority: 1 },
    { url: `${baseUrl}/blog`, lastModified: new Date(), changeFrequency: "daily", priority: 0.9 },
    ...posts,
  ];
}

Gros site ? Pensez à découper le sitemap en plusieurs fichiers et à automatiser la génération pour rester léger et frais.


3) Données structurées JSON‑LD (Article, Breadcrumbs, etc.)

Les moteurs adorent les données structurées. Pour un article de blog, utilisez le schéma Article. Ajoutez un petit composant client pour injecter du JSON‑LD en sécurité.

// components/JsonLd.tsx
"use client";
import Script from "next/script";

export default function JsonLd({ id, data }: { id: string; data: any }) {
  return (
    <Script
      id={id}
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
    />
  );
}
// app/blog/[slug]/page.tsx (dans le rendu)
import JsonLd from "@/components/JsonLd";

const schema = {
  "@context": "https://schema.org",
  "@type": "Article",
  headline: post.title,
  description: post.description,
  datePublished: post.date,
  author: post.author ? [{ "@type": "Person", name: post.author }] : undefined,
  image: post.coverImage ? [`https://example.com${post.coverImage}`] : undefined,
  mainEntityOfPage: `https://example.com/blog/${post.slug}`,
};

return (
  <article className="prose">
    {/* ...contenu... */}
    <JsonLd id="blogpost-jsonld" data={schema} />
  </article>
);

Ajoutez BreadcrumbList sur les pages d’articles pour de meilleurs sitelinks.


4) Des images qui rankent (et passent le LCP)

Utilisez <Image /> partout : tailles responsives, texte alternatif, et priority pour les éléments au‑dessus de la ligne de flottaison.

import Image from "next/image";

export default function Cover({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={1200}
      height={630}
      priority
      sizes="(max-width: 768px) 100vw, 1200px"
      className="rounded-xl"
    />
  );
}

Checklist :

  • alt descriptif (sans bourrage de mots‑clés)
  • Dimensions width/height correctes pour éviter la CLS
  • Utilisez AVIF/WebP quand c’est possible
  • Marquez l’image principale en priority pour aider le LCP

5) SEO international & multi‑locale (optionnel)

Exposez hreflang via alternates.languages :

// app/layout.tsx (metadata)
alternates: {
  canonical: "/",
  languages: {
    "en-US": "/en",
    "fr-FR": "/fr",
  },
},

Assurez‑vous que le routage reflète les locales (/en/...) et que le contenu est réellement localisé (pas juste des métadonnées traduites).


6) Fraîcheur avec ISR, cache et revalidation

Garder du statique rapide et à jour.

// app/blog/[slug]/page.tsx
export const revalidate = 60 * 60; // régénérer au plus une fois par heure

// Si vous déclenchez ailleurs, pensez aux tags :
// import { revalidateTag } from "next/cache";
// revalidateTag("posts");

Utilisez fetch(..., { next: { revalidate: 3600, tags: ["posts"] } }) sur les requêtes de données pour aligner pages et data.


7) Images OG dynamiques (gros gain de CTR)

Générez des aperçus uniques par article pour les réseaux sociaux / Slack.

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default async function OG({ params }: { params: { slug: string } }) {
  // vous pouvez charger les données de l’article ici
  const title = decodeURIComponent(params.slug).replace(/-/g, " ");
  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          width: "100%",
          height: "100%",
          alignItems: "center",
          justifyContent: "center",
          fontSize: 72,
          fontWeight: 800,
        }}
      >
        {title}
      </div>
    ),
    { ...size }
  );
}

8) Contenu & maillage interne (faible effort, gros impact)

  • Un sujet principal par page ; utilisez un <h1> clair et une hiérarchie logique de titres.
  • Liez les articles connexes avec <a> / <Link> et un texte d’ancre descriptif.
  • Gardez des slugs courts, lisibles et stables (/blog/boost-seo-with-nextjs).

9) Analytics & monitoring

  • Suivez les Core Web Vitals (LCP, CLS, INP) et corrigez tôt.
  • Ajoutez l’analytics avec next/script et strategy: "afterInteractive" pour ne pas bloquer le rendu.

10) Pièges courants à éviter

  • Canonique manquante sur listes paginées/filtrées → contenu dupliqué
  • Images lourdes sans dimensions définies → la CLS explose
  • Pas de robots.txt/sitemap.xml → couverture d’exploration incomplète
  • Contenu critique rendu uniquement côté client → préférez le rendu serveur quand c’est possible

Checklist de pré‑publication SEO

  • Titre et meta description uniques
  • Canonique définie (et hreflang si multilingue)
  • Open Graph/Twitter images présentes
  • JSON‑LD Article + (optionnel) BreadcrumbList
  • Images optimisées (<Image>, alt, tailles correctes)
  • LCP < 2,5s mobile ; CLS < 0,1 ; INP < 200ms
  • robots.txt et sitemap.xml générés
  • Liens internes vers/depuis des contenus liés

En résumé : Next.js fournit les bons primitifs pour rendre le SEO simple — HTML pré‑rendu, Metadata API puissante, données structurées faciles, et des outils de performance pour viser les Core Web Vitals. Une fois les bons patterns en place, chaque nouvelle page est prête à ranker.


Août 2025

Prêt à transformer votre présence numérique

Embarquons ensemble pour un voyage d’innovation et de créativité.