Tous les articles
·Ingénierie·9 min

SEO avec Next.js : initialisation et bonnes pratiques de la page d'accueil

Comment initialiser un projet Next.js et configurer le SEO correctement dès le départ : metadata, Open Graph, sitemap, JSON-LD. Fil rouge : la page d'accueil de Kreio.

Un projet Next.js se crée en deux minutes. Le configurer pour que Google le comprenne correctement, c'est une autre histoire. La Metadata API de Next.js, les fichiers de convention (opengraph-image, sitemap, robots), et le balisage structuré JSON-LD forment un ensemble cohérent, mais il faut savoir dans quel ordre les poser et pourquoi.

On va construire tout ça sur un exemple concret : la page d'accueil de Kreio, l'agence web. Chaque brique SEO sera illustrée avec le code réel qu'on utiliserait pour ce projet.

Éditeur de code avec la structure d'un projet Next.js App Router ouvert
L'App Router de Next.js regroupe la logique SEO au plus proche des routes, sans configuration centrale lourde.

Initialiser le projet avec les bons choix dès le départ

pnpm create next-app kreio --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd kreio

Les options qui comptent pour le SEO :

  • --typescript : la Metadata API est typée, TypeScript attrape les erreurs de configuration avant le build.
  • --app : l'App Router est le seul à supporter la Metadata API native de Next.js.
  • --src-dir : sépare le code applicatif de la configuration à la racine.

La structure de départ pour kreio.fr :

src/
  app/
    layout.tsx          ← metadata globale (title template, metadataBase)
    page.tsx            ← page d'accueil avec sa metadata
    opengraph-image.tsx ← OG image générée dynamiquement
    sitemap.ts          ← sitemap généré côté serveur
    robots.ts           ← robots.txt

Le layout racine : poser les fondations globales

Le fichier app/layout.tsx est le point de départ de toute la configuration SEO. C'est ici qu'on définit le metadataBase, le title template, et les valeurs par défaut qui s'appliquent à toutes les pages.

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

export const metadata: Metadata = {
  metadataBase: new URL("https://kreio.fr"),
  title: {
    default: "Kreio — Agence web sur-mesure",
    template: "%s | Kreio",
  },
  description:
    "Kreio conçoit et développe des sites et applications web sur-mesure pour les entreprises françaises et européennes.",
  keywords: [
    "agence web",
    "développement web sur-mesure",
    "Next.js",
    "TypeScript",
    "site vitrine",
    "application web",
  ],
  authors: [{ name: "Oliwer Skweres", url: "https://kreio.fr" }],
  creator: "Kreio",
  publisher: "Kreio",
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      "max-image-preview": "large",
    },
  },
  alternates: {
    canonical: "https://kreio.fr",
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="fr">
      <body>{children}</body>
    </html>
  );
}

Trois points importants ici.

metadataBase est indispensable. Sans lui, Next.js ne peut pas résoudre les URLs relatives dans openGraph.images et affiche un avertissement à chaque build. Il faut le définir une seule fois dans le layout racine.

title.template permet à chaque page de définir uniquement son propre titre. Si app/services/page.tsx exporte title: "Services", le rendu final sera Services | Kreio sans duplication de code.

lang="fr" sur la balise html est un signal fort pour les moteurs de recherche sur la langue du contenu. Ne pas l'oublier.

La metadata de la page d'accueil

La page d'accueil a ses propres balises qui précisent le contexte de la route /.

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

export const metadata: Metadata = {
  title: "Agence web sur-mesure — Next.js & TypeScript",
  description:
    "Kreio développe des sites vitrines, applications web et plateformes SaaS pour les PME françaises. Stack moderne : Next.js, TypeScript, Tailwind CSS.",
  alternates: {
    canonical: "https://kreio.fr",
  },
  openGraph: {
    title: "Kreio — Agence web sur-mesure",
    description:
      "Sites vitrines, applications web et SaaS pour les PME françaises. Next.js, TypeScript, Tailwind CSS.",
    url: "https://kreio.fr",
    siteName: "Kreio",
    locale: "fr_FR",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Kreio — Agence web sur-mesure",
    description: "Sites vitrines et applications web sur-mesure pour les PME françaises.",
    creator: "@kreio_fr",
  },
};

export default function HomePage() {
  return (
    <main>
      <h1>Agence web sur-mesure en France</h1>
      {/* contenu de la page */}
    </main>
  );
}

Le canonical sur la page d'accueil est important. Il indique à Google qu'il n'existe qu'une seule URL canonique pour cette page et évite les problèmes de contenu dupliqué si le site est accessible via www.kreio.fr et kreio.fr en même temps.

BaliseRôle SEOPriorité
titleTitre affiché dans les résultats GoogleCritique
descriptionExtrait dans les résultats (160 car. max)Haute
canonicalÉvite le contenu dupliquéHaute
openGraph.titleTitre affiché lors d'un partage socialMoyenne
twitter.cardFormat de la carte Twitter/XMoyenne
keywordsSignal de pertinence thématiqueFaible

L'OG image générée dynamiquement

Une image Open Graph statique (opengraph-image.png dans app/) suffit pour la homepage. Mais Next.js permet aussi de la générer côté serveur avec ImageResponse, ce qui est utile pour personnaliser l'image selon le contenu de la page.

// src/app/opengraph-image.tsx
import { ImageResponse } from "next/og";

export const alt = "Kreio — Agence web sur-mesure";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default function OgImage() {
  return new ImageResponse(
    (
      <div
        style={{
          background: "#0a0a0a",
          width: "100%",
          height: "100%",
          display: "flex",
          flexDirection: "column",
          alignItems: "flex-start",
          justifyContent: "flex-end",
          padding: "60px 72px",
          fontFamily: "sans-serif",
        }}
      >
        <p style={{ color: "#6b7280", fontSize: 20, margin: "0 0 12px" }}>
          Agence web sur-mesure
        </p>
        <h1 style={{ color: "#ffffff", fontSize: 72, fontWeight: 700, margin: 0, lineHeight: 1.1 }}>
          Kreio
        </h1>
        <p style={{ color: "#9ca3af", fontSize: 24, margin: "16px 0 0" }}>
          Next.js · TypeScript · Tailwind CSS
        </p>
      </div>
    ),
    size,
  );
}

Next.js détecte automatiquement le fichier opengraph-image.tsx et génère les balises og:image correspondantes sans configuration supplémentaire. La même mécanique s'applique pour un fichier statique opengraph-image.png placé au même endroit.

Le sitemap et le fichier robots

Ces deux fichiers sont générés programmatiquement via des fichiers de convention à la racine du dossier app/.

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

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: "https://kreio.fr",
      lastModified: new Date(),
      changeFrequency: "monthly",
      priority: 1,
    },
    {
      url: "https://kreio.fr/services",
      lastModified: new Date(),
      changeFrequency: "monthly",
      priority: 0.8,
    },
    {
      url: "https://kreio.fr/articles",
      lastModified: new Date(),
      changeFrequency: "weekly",
      priority: 0.7,
    },
    {
      url: "https://kreio.fr/contact",
      lastModified: new Date(),
      changeFrequency: "yearly",
      priority: 0.5,
    },
  ];
}
// src/app/robots.ts
import type { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: "*",
      allow: "/",
      disallow: ["/api/", "/_next/"],
    },
    sitemap: "https://kreio.fr/sitemap.xml",
  };
}

Ces deux fichiers sont accessibles automatiquement sur /sitemap.xml et /robots.txt après le build. Pas de configuration supplémentaire, pas de plugin externe nécessaire.

Le balisage structuré JSON-LD

Le JSON-LD permet à Google d'afficher des rich snippets dans les résultats de recherche : breadcrumbs, notes, FAQ, ou pour une organisation, les informations de contact directement dans le knowledge panel.

Pour la page d'accueil de Kreio, on utilise le schema Organization :

// src/app/page.tsx (extrait — ajouter dans le composant)
export default function HomePage() {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Organization",
    name: "Kreio",
    url: "https://kreio.fr",
    logo: "https://kreio.fr/logo.png",
    description:
      "Agence web sur-mesure spécialisée en Next.js, TypeScript et Tailwind CSS.",
    address: {
      "@type": "PostalAddress",
      addressLocality: "Évreux",
      addressRegion: "Normandie",
      addressCountry: "FR",
    },
    contactPoint: {
      "@type": "ContactPoint",
      contactType: "customer service",
      email: "hello@kreio.fr",
    },
    sameAs: [
      "https://github.com/kreio",
      "https://www.linkedin.com/company/kreio",
    ],
  };

  return (
    <main>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <h1>Agence web sur-mesure en France</h1>
    </main>
  );
}

La structure sémantique HTML : ce que Google lit vraiment

Les balises meta sont une chose. La structure du HTML en est une autre. Google analyse les deux. Sur la homepage de Kreio, les règles à respecter :

export default function HomePage() {
  return (
    <main>
      {/* Un seul h1 par page, contenant le mot-clé principal */}
      <h1>Agence web sur-mesure en France</h1>

      {/* Les h2 structurent le contenu comme des chapitres */}
      <section aria-labelledby="services-title">
        <h2 id="services-title">Nos services</h2>
        <p>Développement de sites vitrines, applications web et SaaS.</p>
      </section>

      <section aria-labelledby="stack-title">
        <h2 id="stack-title">Notre stack technique</h2>
        <p>Next.js, TypeScript, Tailwind CSS, Prisma.</p>
      </section>
    </main>
  );
}
  • Un seul h1 par page, qui reprend le mot-clé principal de la page.
  • Les h2 doivent être descriptifs et contenir des variantes du mot-clé principal.
  • aria-labelledby relie la section à son titre, ce qui améliore l'accessibilité et la compréhension sémantique par les crawlers.
  • Le contenu doit être présent dans le HTML initial, pas chargé via un useEffect côté client. Les Server Components de Next.js garantissent ça par défaut.

Checklist avant le premier déploiement

Avant de pousser le projet en production, voici les vérifications à faire dans l'ordre :

  • metadataBase défini dans app/layout.tsx
  • title.template configuré avec le nom de la marque
  • lang="fr" sur la balise html
  • canonical défini sur chaque page
  • opengraph-image.tsx ou opengraph-image.png présent dans app/
  • sitemap.ts retourne toutes les URLs publiques
  • robots.ts autorise le crawl et pointe vers le sitemap
  • JSON-LD validé via Rich Results Test
  • Un seul h1 par page, contenu rendu côté serveur
  • pnpm build passe sans erreur ni avertissement metadata

Ce qu'on en retient

Le SEO avec Next.js App Router n'est pas compliqué, mais il demande de la rigueur dès l'initialisation. Les erreurs classiques (pas de metadataBase, contenu rendu côté client, OG images manquantes, un seul sitemap vide) se corrigent facilement si on les prend en amont. Les reprendre après coup sur un projet en production coûte plus cher.

Le fil rouge Kreio montre que toutes ces briques s'articulent naturellement : le layout pose les fondations, chaque page précise son contexte, et les fichiers de convention (sitemap, robots, OG image) s'ajoutent au fur et à mesure sans configuration externe.

Sources

  1. Metadata API — Documentation officielle Next.js
  2. opengraph-image et twitter-image — Next.js File Conventions
  3. Rich Results Test — Google Search Console