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.