Retour au blog

Construire une landing page React avec des sections à copier-coller

Publié le 13 mars 2026·6 min read

Les landing pages suivent une formule éprouvée. Les visiteurs arrivent avec une question — "est-ce que ça peut résoudre mon problème ?" — et chaque section les rapproche du "oui" ou les fait décrocher. Ce guide parcourt la formule en 8 sections, explique comment composer ces sections dans Next.js App Router, et détaille les patterns techniques qui rendent le résultat rapide et maintenable.

La formule de la landing page

Les études en optimisation de conversion convergent vers le même ordre de sections. Voici la séquence et la raison d'être de chaque section :

| # | Section | Rôle | |---|---------|------| | 1 | Hero | Énoncer la proposition de valeur. Capter l'attention. | | 2 | Logo Carousel | Établir la preuve sociale juste après le hook. | | 3 | Features | Montrer comment le produit tient sa promesse. | | 4 | Social Proof | Témoignages ou études de cas — la preuve par de vraies personnes. | | 5 | Product Showcase | Démo visuelle, carousel de screenshots ou walkthrough. | | 6 | Pricing | Lever l'objection "combien ça coûte ?". | | 7 | FAQ | Traiter les dernières objections avant l'achat. | | 8 | CTA | Une dernière invitation à agir, avec un maximum de facilité. |

Ce n'est pas une formule rigide — un produit B2B enterprise pourrait supprimer le pricing public et le remplacer par "Contacter les ventes". Une appli grand public pourrait commencer par la preuve sociale. Mais cet ordre offre le meilleur taux de conversion de base pour les SaaS en self-serve.

Composer les sections dans Next.js App Router

Dans Next.js 15 avec App Router, votre landing page se trouve à app/[locale]/(public)/page.tsx. Chaque section est un fichier séparé dans src/registry/sections/ :

// app/[locale]/(public)/page.tsx
import HeroCentered from "@/registry/sections/hero/hero-centered/HeroCentered";
import LogoCarousel from "@/registry/sections/logo-carousel/logo-carousel-marquee/LogoCarouselMarquee";
import FeaturesGrid from "@/registry/sections/features/features-grid/FeaturesGrid";
import Testimonials from "@/registry/sections/testimonials/testimonials-cards/TestimonialsCards";
import PricingToggle from "@/registry/sections/pricing/pricing-toggle/PricingToggle";
import FAQ from "@/registry/sections/faq/faq-accordion/FaqAccordion";
import CTABanner from "@/registry/sections/cta/cta-centered/CtaCentered";

import { heroMock } from "@/registry/sections/hero/hero-centered/mock";
import { pricingMock } from "@/registry/sections/pricing/pricing-toggle/mock";

export default function HomePage() {
  return (
    <main>
      <HeroCentered {...heroMock} />
      <LogoCarousel />
      <FeaturesGrid id="features" />
      <Testimonials id="social-proof" />
      <PricingToggle id="pricing" {...pricingMock} />
      <FAQ id="faq" />
      <CTABanner />
    </main>
  );
}

Les Server Components se rendent instantanément au build (ou à la requête). La directive "use client" n'apparaît que sur les sections qui nécessitent de l'interactivité (toggle, animations). La majorité de votre landing page est donc du HTML statique — excellent pour les Core Web Vitals.

Séparateurs de sections

Les sections avec la même couleur de fond se fondent visuellement les unes dans les autres. Un composant séparateur crée du rythme et une séparation visuelle sans ajouter des <div> vides :

// components/SectionDivider.tsx
interface SectionDividerProps {
  variant?: "line" | "wave" | "angle" | "none";
  flip?: boolean;
}

export function SectionDivider({ variant = "line", flip = false }: SectionDividerProps) {
  if (variant === "none") return null;

  if (variant === "line") {
    return (
      <div
        style={{
          height: "1px",
          background: "var(--color-border)",
          margin: "0 var(--container-padding-x)",
        }}
      />
    );
  }

  if (variant === "wave") {
    return (
      <div
        style={{
          overflow: "hidden",
          transform: flip ? "scaleY(-1)" : "none",
          lineHeight: 0,
        }}
      >
        <svg
          viewBox="0 0 1440 56"
          style={{ display: "block", width: "100%" }}
          aria-hidden
        >
          <path
            d="M0,28 C360,56 1080,0 1440,28 L1440,56 L0,56 Z"
            fill="var(--color-background-alt)"
          />
        </svg>
      </div>
    );
  }

  return null;
}

Utilisez-le entre les sections avec des fonds différents :

<HeroCentered />
<SectionDivider variant="wave" />
<FeaturesGrid />

Navigation par ancre

Pour les landing pages mono-page, les liens d'ancre permettent aux utilisateurs de sauter directement aux sections. Ajoutez des attributs id aux wrappers de sections et liez-les avec #pricing, #features, etc. :

// Pass an id to each section component
interface SectionProps {
  id?: string;
}

// Inside the section component
<section
  id={id}
  style={{
    scrollMarginTop: "4rem", // Account for the fixed navbar height
    paddingTop: "var(--section-padding-y)",
    paddingBottom: "var(--section-padding-y)",
    background: "var(--color-background)",
  }}
>

La propriété CSS scroll-margin-top est critique — sans elle, la navbar fixe recouvre le titre de la section quand le lien d'ancre se déclenche.

Dans la navbar :

const navLinks = [
  { label: "Features", href: "#features" },
  { label: "Pricing", href: "#pricing" },
  { label: "FAQ", href: "#faq" },
];

Pour un défilement fluide, ajoutez à votre CSS global :

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

Chargement différé des sections sous le fold

Le hero et la première section visible doivent se charger immédiatement. Tout ce qui est sous le fold peut être chargé en lazy avec dynamic de Next.js :

import dynamic from "next/dynamic";

// Load immediately (above the fold)
import HeroCentered from "@/registry/sections/hero/hero-centered/HeroCentered";
import LogoCarousel from "@/registry/sections/logo-carousel/logo-carousel-marquee/LogoCarouselMarquee";

// Lazy load (below the fold)
const FeaturesGrid = dynamic(
  () => import("@/registry/sections/features/features-grid/FeaturesGrid"),
  { loading: () => <SectionSkeleton height={500} /> }
);
const Testimonials = dynamic(
  () => import("@/registry/sections/testimonials/testimonials-cards/TestimonialsCards"),
  { loading: () => <SectionSkeleton height={400} /> }
);
const PricingToggle = dynamic(
  () => import("@/registry/sections/pricing/pricing-toggle/PricingToggle"),
  { loading: () => <SectionSkeleton height={600} /> }
);

Le composant SectionSkeleton réserve l'espace vertical pour éviter les décalages de layout pendant le chargement de la section :

function SectionSkeleton({ height }: { height: number }) {
  return (
    <div
      style={{
        height,
        background: "var(--color-background)",
        animation: "pulse 1.5s ease-in-out infinite",
      }}
    />
  );
}

Cette approche réduit le bundle JavaScript initial et déplace le JS non critique vers des requêtes réseau ultérieures, améliorant le Time to Interactive (TTI).

Pattern d'animation des sections

Chaque section devrait s'animer quand elle entre dans le viewport. Le pattern canonique utilise whileInView de Framer Motion avec viewport={{ once: true }} :

import { motion } from "framer-motion";

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

// Section header
<motion.div
  initial={{ opacity: 0, y: 24 }}
  whileInView={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.6, ease }}
  viewport={{ once: true, margin: "-80px" }}
>
  <h2>Section heading</h2>
</motion.div>

Le margin: "-80px" déclenche l'animation 80px avant que l'élément entre dans le viewport, pour qu'elle soit déjà en cours quand l'utilisateur le voit — et non après.

Tout assembler

Une structure complète de landing page Next.js :

app/[locale]/(public)/
├── page.tsx           ← composition des sections
└── layout.tsx         ← layout partagé (navbar, footer)

src/registry/sections/
├── hero/hero-centered/
├── logo-carousel/logo-carousel-marquee/
├── features/features-grid/
├── testimonials/testimonials-cards/
├── pricing/pricing-toggle/
├── faq/faq-accordion/
└── cta/cta-centered/

Chaque dossier de section contient trois fichiers : le composant (PascalCase.tsx), ses métadonnées (meta.ts) et ses données mock (mock.ts). Cela rend chaque section indépendante et portable — copiez les trois fichiers dans n'importe quel autre projet et ça fonctionne.

Parcourez toutes les sections

Le catalogue Incubator propose 449 variantes de sections dans 66 types — heroes, features, témoignages, pricing, FAQs, CTAs, footers, et bien plus. Chaque variante est construite avec le même système de tokens CSS et est prête à copier-coller dans vos projets Next.js.

VA

Victor Aubague

Développeur & créateur d'Incubator

Développeur full-stack spécialisé en React, Next.js et TypeScript. J'ai créé Incubator pour aider les développeurs à livrer de belles interfaces plus rapidement — tous les composants sont issus de vrais projets clients et de code en production.

LinkedIn
Construire une landing page React avec des sections à copier-coller — Incubator