Landing pages con Claude Code: oferta clara, confianza, métricas y pruebas
Implementa landing pages con Claude Code, Astro/React, formularios, eventos, A/B testing y QA móvil sin promesas falsas.
Una landing page es un recorrido de decisión, no una maqueta bonita
Claude Code puede generar una landing page en minutos: hero, tarjetas de características, precios, FAQ y botones. El problema es que una página bonita no siempre convierte. Una landing page útil debe explicar una oferta concreta a una persona concreta, resolver dudas de confianza, mostrar el siguiente paso y dejar datos suficientes para mejorarla después.
Conversión no significa solo pago. Puede ser reservar una consultoría, descargar una guía, comprar una plantilla, pedir formación para un equipo o empezar una prueba. CTA significa Call To Action, o llamada a la acción: el botón que dice “Reservar revisión”, “Descargar checklist” o “Ver plantillas”. Un bloque de confianza es la parte que demuestra por qué el lector debería creerte: proceso, experiencia, revisión técnica, resultados verificados y límites honestos.
En ClaudeCodeLab, el objetivo de esta landing no es prometer un aumento garantizado de conversión. El objetivo es conectar consultoría de Claude Code, plantillas y formación con una página clara, medible, accesible y rápida. Usa documentación oficial como base: Claude Code docs, Astro Pages, Tailwind CSS, React forms, GA4 events y Playwright.
Tres casos de uso antes del diseño
Antes de pedir componentes, define para quién es la página. Esta tabla ayuda a Claude Code a evitar texto genérico.
| Caso de uso | Estado del lector | Oferta adecuada | Evento clave |
|---|---|---|---|
| Venta de plantilla para indie hackers | Quiere crear una LP con Claude Code pero no sabe estructurar el copy | Plantilla Astro LP y prompts de copy de conversión | Clic en producto e inicio de checkout |
| Consultoría SaaS o agencia | Tiene tráfico, pero la calidad del lead es irregular | Diagnóstico de 90 minutos y revisión de implementación | Envío de formulario y reserva |
| Formación de equipo | Quiere que el equipo use Claude Code con criterio común | Workshop con implementación, review, analítica y pruebas | Solicitud de formación y descarga de dossier |
El lector decide en orden: “¿esto es para mí?”, “¿puedo confiar?”, “¿qué pasa si hago clic?”. La página debe responder antes de pedir un correo o dinero.
flowchart TD
A["Tráfico desde búsqueda, artículo, anuncio o redes"] --> B["Primer viewport con audiencia, oferta y CTA"]
B --> C["Bloques de confianza explican el proceso"]
C --> D["Casos de uso conectan con la situación del lector"]
D --> E["Precio o lead magnet reduce la fricción"]
E --> F["Formulario, clic de producto o consulta de formación"]
F --> G["Eventos y A/B test alimentan la siguiente mejora"]
Prompt de producción para Claude Code
Un buen resultado empieza con una petición específica.
Implementa una landing page para "Claude Code Landing Page Sprint" con Astro, React y Tailwind CSS.
Objetivo:
- Convertir lectores en reservas de consultoría, compras de plantillas o solicitudes de formación.
- Audiencia: founders, operadores SaaS, agencias y responsables de ingeniería.
- No prometer mejoras garantizadas de conversión.
Secciones obligatorias:
- Primer viewport: audiencia, oferta, CTA principal y CTA secundario.
- Bloque de confianza: experiencia práctica de Masa, proceso de review y checklist.
- Tres casos de uso concretos.
- Precio o lead magnet.
- Formulario: name, email, company, goal, budget, consent.
- Eventos: lp_view, cta_click, lead_submit, product_click.
- A/B test: comparar dos textos de CTA.
- Playwright mobile checks: CTA visible, labels correctas y sin scroll horizontal.
Restricciones:
- TypeScript copiable, no pseudocódigo.
- Labels, focus, teclado y contraste correctos.
- Evitar vídeo de fondo pesado que empeore LCP.
- No enviar datos personales a eventos de analítica.
Primer viewport con Astro
El primer viewport debe decir quién recibe valor, qué se entrega y qué acción sigue. Este componente mantiene dos variantes de copy para poder testearlas.
---
// src/components/LandingHero.astro
export interface Props {
variant: "control" | "lead_magnet";
}
const { variant } = Astro.props;
const copy = {
control: {
eyebrow: "Claude Code Landing Page Sprint",
headline: "Lanza una landing de Claude Code con oferta, formulario y tracking",
body: "Convertimos la página en un embudo medible: primer viewport, confianza, precios, formulario, eventos y QA móvil.",
primary: "Reservar revisión gratuita",
secondary: "Ver plantillas",
},
lead_magnet: {
eyebrow: "Checklist gratis incluida",
headline: "Detecta los huecos de conversión antes de escribir la UI",
body: "Revisa oferta, CTA, precio, formulario, velocidad, accesibilidad y eventos antes de publicar.",
primary: "Recibir checklist",
secondary: "Ver formación",
},
}[variant];
---
<section class="bg-slate-950 px-4 py-16 text-white sm:py-20">
<div class="mx-auto grid max-w-6xl gap-10 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
<div>
<p class="text-sm font-semibold uppercase tracking-wide text-cyan-300">{copy.eyebrow}</p>
<h1 class="mt-4 max-w-3xl text-4xl font-bold leading-tight sm:text-5xl">{copy.headline}</h1>
<p class="mt-5 max-w-2xl text-lg leading-8 text-slate-200">{copy.body}</p>
<div class="mt-8 flex flex-col gap-3 sm:flex-row">
<a data-cta-id="hero-primary" href="#lead-form" class="inline-flex min-h-12 items-center justify-center rounded-md bg-cyan-300 px-6 font-semibold text-slate-950 hover:bg-cyan-200 focus:outline-none focus:ring-2 focus:ring-cyan-200 focus:ring-offset-2 focus:ring-offset-slate-950">{copy.primary}</a>
<a data-cta-id="hero-secondary" href="/products" class="inline-flex min-h-12 items-center justify-center rounded-md border border-slate-500 px-6 font-semibold text-white hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-cyan-200 focus:ring-offset-2 focus:ring-offset-slate-950">{copy.secondary}</a>
</div>
<p class="mt-4 text-sm text-slate-400">Sin promesas de mejora garantizada. La promesa es claridad, implementación y medición.</p>
</div>
<div class="rounded-lg border border-slate-700 bg-slate-900 p-6">
<h2 class="text-lg font-semibold">Checklist antes de publicar</h2>
<ul class="mt-4 space-y-3 text-sm leading-6 text-slate-200">
<li>Audiencia, oferta y siguiente paso se entienden en el primer viewport.</li>
<li>La confianza se apoya en proceso, experiencia y revisión.</li>
<li>Formulario, producto y formación se miden con eventos distintos.</li>
<li>En móvil, CTA y formulario siguen siendo utilizables.</li>
</ul>
</div>
</div>
</section>
Los atributos data-cta-id permiten medir el botón exacto sin depender del texto visible. Esto evita romper dashboards cuando cambias el copy.
Confianza, pruebas, precio y lead magnet
La parte media de la página debe eliminar objeciones en el orden en que aparecen.
| Bloque | Qué mostrar | Qué evitar |
|---|---|---|
| Confianza | Checklist, capturas, biografía del responsable, proceso QA | Sellos vacíos y frases “AI-powered” |
| Prueba | Qué probó Masa, qué falló, qué se corrigió | Testimonios inventados o cifras imposibles de verificar |
| Precio | Diferencia entre plantilla, review y formación | Ocultar todos los precios detrás de un formulario |
| Lead magnet | Checklist, auditoría, prompts de copy | Un PDF débil solo para capturar emails |
Para ClaudeCodeLab, la escalera natural es products para quien quiere hacerlo por su cuenta, training para equipos y formulario para una revisión puntual.
Form schema y API en Astro
Un formulario necesita un contrato de datos. Este endpoint no requiere dependencias adicionales.
// src/pages/api/lead.ts
import type { APIRoute } from "astro";
type LeadInput = {
name: string;
email: string;
company: string;
goal: string;
budget: "template" | "consulting" | "training" | "undecided";
consent: boolean;
};
function validateLead(form: FormData): { ok: true; data: LeadInput } | { ok: false; errors: string[] } {
const data: LeadInput = {
name: String(form.get("name") ?? "").trim().slice(0, 80),
email: String(form.get("email") ?? "").trim().slice(0, 120),
company: String(form.get("company") ?? "").trim().slice(0, 120),
goal: String(form.get("goal") ?? "").trim().slice(0, 1000),
budget: String(form.get("budget") ?? "undecided") as LeadInput["budget"],
consent: form.get("consent") === "on",
};
const errors: string[] = [];
if (!data.name) errors.push("Name is required.");
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) errors.push("A valid email is required.");
if (data.goal.length < 20) errors.push("Please describe the goal in at least 20 characters.");
if (!["template", "consulting", "training", "undecided"].includes(data.budget)) errors.push("Budget is invalid.");
if (!data.consent) errors.push("Consent is required.");
return errors.length ? { ok: false, errors } : { ok: true, data };
}
export const POST: APIRoute = async ({ request }) => {
const result = validateLead(await request.formData());
if (!result.ok) {
return new Response(JSON.stringify({ ok: false, errors: result.errors }), {
status: 400,
headers: { "content-type": "application/json" },
});
}
console.info("new_lp_lead", {
emailDomain: result.data.email.split("@")[1],
budget: result.data.budget,
goalLength: result.data.goal.length,
});
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
};
La analítica no debe recibir nombre, email, empresa ni texto libre. Guarda datos personales en el sistema de leads, no en eventos de marketing.
// src/components/LeadForm.tsx
import { useState } from "react";
import { trackLpEvent } from "../lib/lp-events";
export function LeadForm() {
const [message, setMessage] = useState("");
const [sending, setSending] = useState(false);
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setSending(true);
const response = await fetch("/api/lead", {
method: "POST",
body: new FormData(event.currentTarget),
});
setSending(false);
if (!response.ok) {
const body = await response.json();
setMessage(body.errors?.join(" ") ?? "Revisa el formulario.");
return;
}
trackLpEvent({ eventName: "lead_submit", ctaId: "lead-form", value: "consulting" });
setMessage("Solicitud enviada. Revisa tu correo para el siguiente paso.");
event.currentTarget.reset();
}
return (
<form id="lead-form" onSubmit={onSubmit} className="space-y-5 rounded-lg border border-slate-200 p-6">
<label className="block text-sm font-medium">Nombre<input name="name" required className="mt-1 w-full rounded-md border px-3 py-2" /></label>
<label className="block text-sm font-medium">Email<input name="email" type="email" required className="mt-1 w-full rounded-md border px-3 py-2" /></label>
<label className="block text-sm font-medium">Objetivo de la landing<textarea name="goal" required minLength={20} rows={5} className="mt-1 w-full rounded-md border px-3 py-2" /></label>
<label className="block text-sm font-medium">Tipo de ayuda
<select name="budget" className="mt-1 w-full rounded-md border px-3 py-2">
<option value="template">Plantilla</option>
<option value="consulting">Consultoría</option>
<option value="training">Formación de equipo</option>
<option value="undecided">Aún no lo sé</option>
</select>
</label>
<label className="flex gap-2 text-sm"><input name="consent" type="checkbox" required />Acepto que se me contacte sobre esta solicitud.</label>
<button disabled={sending} className="min-h-11 w-full rounded-md bg-slate-950 px-5 font-semibold text-white disabled:opacity-60">{sending ? "Enviando..." : "Enviar solicitud"}</button>
<p role="status" aria-live="polite" className="text-sm">{message}</p>
</form>
);
}
Eventos, velocidad y A/B testing
Fija los nombres antes de publicar: lp_view, cta_click, lead_submit, product_click.
// src/lib/lp-events.ts
type LpEventName = "lp_view" | "cta_click" | "lead_submit" | "product_click";
type LpEvent = {
eventName: LpEventName;
ctaId?: string;
variant?: "control" | "lead_magnet";
value?: "template" | "consulting" | "training";
};
declare global {
interface Window {
dataLayer?: Array<Record<string, unknown>>;
gtag?: (command: "event", name: string, params: Record<string, unknown>) => void;
}
}
export function trackLpEvent(event: LpEvent) {
if (typeof window === "undefined") return;
const params = {
page_slug: "claude-code-landing-page",
cta_id: event.ctaId,
variant: event.variant,
value_type: event.value,
};
window.dataLayer?.push({ event: event.eventName, ...params });
window.gtag?.("event", event.eventName, params);
}
Incluye rendimiento en la revisión. Core Web Vitals explica LCP, INP y CLS. En una landing, los problemas típicos son imágenes hero pesadas, scripts de terceros, formularios que cargan tarde y bloques de prueba que mueven el layout.
Para A/B testing, compara hipótesis concretas, no intuiciones. Si el tráfico es bajo, no declares un ganador absoluto.
// src/lib/landing-ab.ts
export type LandingVariant = "control" | "lead_magnet";
export function chooseLandingVariant(visitorId: string): LandingVariant {
let hash = 2166136261;
for (let index = 0; index < visitorId.length; index += 1) {
hash ^= visitorId.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return Math.abs(hash) % 2 === 0 ? "control" : "lead_magnet";
}
---
// src/pages/lp.astro
import LandingHero from "../components/LandingHero.astro";
import { chooseLandingVariant } from "../lib/landing-ab";
const visitorId = Astro.cookies.get("lp_visitor")?.value ?? crypto.randomUUID();
Astro.cookies.set("lp_visitor", visitorId, {
path: "/",
sameSite: "lax",
secure: import.meta.env.PROD,
maxAge: 60 * 60 * 24 * 30,
});
const variant = chooseLandingVariant(visitorId);
---
<LandingHero variant={variant} />
<script define:vars={{ variant }}>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ event: "lp_view", page_slug: "claude-code-landing-page", variant });
</script>
Evita asignar variantes solo con localStorage; puede provocar parpadeo inicial y datos inconsistentes.
QA móvil con Playwright
// tests/landing-page.spec.ts
import { test, expect, devices } from "@playwright/test";
test.use({ ...devices["iPhone 13"] });
test("mobile LP keeps CTA visible and avoids horizontal overflow", async ({ page }) => {
await page.goto("/lp");
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
await expect(page.locator('[data-cta-id="hero-primary"]')).toBeVisible();
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
const viewportWidth = page.viewportSize()?.width ?? 390;
expect(scrollWidth).toBeLessThanOrEqual(viewportWidth + 1);
});
test("lead form requires consent", async ({ page }) => {
await page.goto("/lp");
await page.getByLabel("Nombre").fill("Masa");
await page.getByLabel("Email").fill("masa@example.com");
await page.getByLabel("Objetivo de la landing").fill("Quiero mejorar una landing de consultoría de Claude Code.");
await page.getByRole("button", { name: "Enviar solicitud" }).click();
await expect(page.getByLabel("Acepto que se me contacte sobre esta solicitud.")).toBeFocused();
});
Fallos frecuentes
El primer fallo es una oferta vaga. “Automatización con IA” no ayuda. “Implementamos la landing, el formulario, los eventos y el QA móvil de tu oferta Claude Code” sí se puede evaluar.
El segundo es la prueba falsa. No inventes testimonios ni cifras. Muestra proceso, checklist y lo que Masa probó realmente.
El tercero es ocultar todos los caminos detrás de un formulario. Algunas personas quieren comprar una plantilla; otras necesitan formación; otras solo una revisión.
El cuarto es tratar accesibilidad como decoración. Sin labels, contraste y teclado, pierdes conversiones reales.
El quinto es medir tarde. Si los eventos no están definidos antes del lanzamiento, la mejora posterior será opinión.
Conexión con ClaudeCodeLab
Para seguir, revisa products, training, o usa el formulario para una revisión de LP. Para ampliar la implementación, lee analítica con Claude Code, A/B testing con Claude Code, Playwright con Claude Code y SEO con Claude Code.
Resultado práctico
Al probar este flujo, Masa no obtuvo un número mágico de conversión. Lo valioso fue que los CTA ids, el schema del formulario y las pruebas móviles quedaron definidos antes del pulido visual. Así la página dejó de ser una pieza bonita y pasó a ser una base desde la que aprender.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.