Volver al blog

Cómo crear una landing page en React con secciones copy-paste

Publicado el 13 de marzo de 2026·6 min read

Las landing pages siguen una fórmula probada. El visitante llega con una pregunta, "¿esto resuelve mi problema?", y cada sección lo acerca al "sí" o lo pierde por el camino. En esta guía recorremos la fórmula de 8 secciones, cómo componer esas secciones en Next.js App Router y los patrones técnicos que mantienen el resultado rápido y fácil de mantener.

La fórmula de la landing page

Los estudios sobre optimización de conversión coinciden en un mismo orden de secciones. Este es el orden y la razón de ser de cada una:

| # | Sección | Función | |---|---------|---------| | 1 | Hero | Expone la propuesta de valor. Capta la atención. | | 2 | Carrusel de logos | Aporta prueba social justo después del gancho. | | 3 | Características | Muestra cómo el producto cumple la promesa. | | 4 | Prueba social | Testimonios o casos de éxito, la voz de personas reales. | | 5 | Demo del producto | Demo visual, carrusel de capturas o recorrido guiado. | | 6 | Precios | Elimina la objeción del "¿cuánto cuesta?". | | 7 | FAQ | Resuelve las últimas dudas antes de la compra. | | 8 | CTA | Una invitación final, sin fricción, para actuar. |

No es una fórmula rígida. Un producto B2B enterprise puede saltarse los precios públicos y sustituirlos por un "Contacta con ventas". Una app de consumo quizá empiece con la prueba social. Aun así, este orden ofrece la mayor tasa de conversión base para un SaaS self-serve.

Componer secciones en Next.js App Router

En Next.js 15 con App Router, tu landing page vive en app/[locale]/(public)/page.tsx. Cada sección es un archivo independiente dentro de 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>
  );
}

Los Server Components se renderizan al instante en tiempo de build (o bajo petición). La directiva "use client" solo aparece en las secciones que necesitan interactividad (toggle, animaciones). Eso significa que la mayor parte de tu landing page es HTML estático, ideal para los Core Web Vitals.

Separadores de sección

Cuando dos secciones comparten color de fondo, se confunden visualmente. Un componente separador crea aire y separación sin sumar <div>s vacíos:

// 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;
}

Úsalo entre secciones con fondos distintos:

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

Navegación por anclas

En landing pages de una sola página, los enlaces ancla permiten saltar a cada sección. Añade atributos id a los contenedores de sección y enlázalos con #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 propiedad CSS scroll-margin-top es clave: sin ella, el navbar fijo tapa el encabezado de la sección cuando se activa el enlace ancla.

En el navbar:

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

Para un desplazamiento suave, añade esto a tu CSS global:

html {
  scroll-behavior: smooth;
}

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

Lazy loading de las secciones bajo el pliegue

El hero y la primera sección visible deben cargarse de inmediato. Todo lo que queda bajo el pliegue puede cargarse en diferido con 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} /> }
);

El componente SectionSkeleton reserva el espacio vertical para evitar saltos de layout mientras la sección carga:

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

Este enfoque reduce el bundle inicial de JavaScript y mueve el JS no crítico a peticiones de red posteriores, lo que mejora el Time to Interactive (TTI).

Patrón de animación de sección

Cada sección debería animarse al entrar en el viewport. El patrón habitual usa whileInView de Framer Motion junto con 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>

El margin: "-80px" dispara la animación 80px antes de que el elemento entre en el viewport, así ya está animándose cuando el usuario lo ve, y no después.

Ensamblarlo todo

La estructura completa de una landing page en Next.js:

app/[locale]/(public)/
├── page.tsx           ← section composition
└── layout.tsx         ← shared layout (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/

Cada carpeta de sección contiene tres archivos: el componente (PascalCase.tsx), sus metadatos (meta.ts) y sus datos de ejemplo (mock.ts). Así cada sección es portable por sí sola: copia los tres archivos a cualquier otro proyecto y funciona.

Explora todas las secciones

El catálogo de Incubator reúne 449 variantes de sección repartidas en 66 tipos: heroes, características, testimonios, precios, FAQs, CTAs, footers y más. Cada variante se construye con el mismo sistema de tokens CSS y está lista para copy-paste en proyectos Next.js.

Relacionado en incubator

VA

Victor Aubague

Desarrollador y creador de Incubator

Desarrollador full-stack especializado en React, Next.js y TypeScript. Creé Incubator para ayudar a los desarrolladores a lanzar interfaces bonitas más rápido, todos los componentes provienen de proyectos reales de clientes y de código en producción.

LinkedIn

Related articles

Cómo crear una landing page en React con secciones