wandres.dev
EL COMPONENTE .ASTRO · frontmatter y template

Importar y componer componentes

La composición en Astro 7: importar otros componentes .astro en la valla y usarlos como etiquetas, pasar datos por props y recibirlas con Astro.props, anidar contenido mediante slots por defecto y con nombre, y ver el árbol de componentes resolverse entero en el build.

⏱ 14 min

Un componente aislado sirve de poco; su valor aparece cuando se combina con otros. Astro compone como cualquier framework moderno —importas una pieza, la usas como etiqueta, le pasas datos y anidas contenido dentro— pero con una diferencia que ya conoces: todo el árbol se resuelve en el build. Componer en Astro es ensamblar HTML a partir de piezas reutilizables, sin que ninguna de esas piezas cobre vida en el cliente.

🎯 Al terminar esta lección sabrás
  • Importar un componente .astro en la valla y usarlo como una etiqueta en la plantilla.
  • Pasar datos del padre al hijo por props y recibirlos con Astro.props.
  • Anidar contenido con slot y dirigirlo con slots con nombre.
  • Ver el árbol de composición resolverse por completo durante el build.

Importar y usar un componente

La composición empieza en la valla con un import, exactamente como traerías una función de otro módulo. Una vez importado, el componente se usa en la plantilla como si fuera una etiqueta HTML más, con la convención universal de que su nombre empieza por mayúscula para distinguirlo de las etiquetas nativas.

---
import Tarjeta from '../components/Tarjeta.astro';
import Cabecera from '../components/Cabecera.astro';
---
<Cabecera />
<main>
  <Tarjeta />
  <Tarjeta />
</main>

Ese import vive en la valla, así que sigue las reglas de la lección anterior: se resuelve en el build o en el servidor y no engrosa el bundle de cliente. Importar y usar diez componentes .astro no añade un solo byte de JavaScript a la página, porque cada uno se expande a su HTML en el momento del renderizado y desaparece como abstracción.

📝
La mayúscula inicial no es decorativa

Astro decide si una etiqueta es un componente o un elemento HTML por su inicial: mayúscula significa componente que debes haber importado; minúscula significa etiqueta nativa. Por eso <tarjeta /> intentaría emitir una etiqueta HTML desconocida en lugar de tu componente Tarjeta. La convención de mayúscula inicial no es cuestión de estilo, sino semántica que el compilador lee.

Pasar props

Un componente reutilizable necesita variar según el contexto, y eso se logra con props: atributos que el padre escribe en la etiqueta y el hijo recibe a través de Astro.props. La sintaxis de paso es la de los atributos HTML, con llaves para valores que no sean texto literal.

---
// padre
import Tarjeta from '../components/Tarjeta.astro';
const total = 42;
---
<Tarjeta titulo="Bienvenida" destacado total={total} />

En el hijo, la práctica canónica es declarar una interface Props que documente el contrato y desestructurar Astro.props, aprovechando para dar valores por defecto. Así el editor conoce qué props existen, cuáles son obligatorias y de qué tipo, y protesta si el padre se equivoca.

---
// hijo: Tarjeta.astro
interface Props {
  titulo: string;
  total?: number;
  destacado?: boolean;
}
const { titulo, total = 0, destacado = false } = Astro.props;
---
<article class:list={["tarjeta", { destacado }]}>
  <h2>{titulo}</h2>
  <p>Total: {total}</p>
</article>

Los datos fluyen en una sola dirección: del padre al hijo. El hijo lee sus props pero no las modifica ni avisa al padre de cambios, porque no hay cambios que comunicar —todo ocurre una vez, en el build—. Esta unidireccionalidad no es una limitación que sortear, sino el reflejo natural de un modelo donde el árbol se calcula de golpe y no vuelve a moverse.

ℹ️
Atributos taquigráficos y booleanos

Escribir destacado a secas equivale a destacado={true}: un atributo sin valor pasa true, igual que en HTML. Y si tienes una variable con el mismo nombre que la prop, Astro 7 admite la forma abreviada {titulo} en lugar de titulo={titulo}. Son atajos pequeños, pero limpian el marcado cuando pasas muchas props cuyo nombre coincide con el de la variable de origen.

Cuando reenvías un conjunto de props a un hijo sin nombrarlas una a una, el operador de propagación las expande igual que en una llamada de función. Es útil para componentes de envoltura que pasan casi todo hacia abajo:

---
import Enlace from '../components/Enlace.astro';
const atributos = { href: "/blog", class: "destacado" };
---
<Enlace {...atributos}>Ir al blog</Enlace>

El hijo recibe href y class en su Astro.props como si las hubieras escrito una por una. Con moderación, la propagación reduce la repetición; en exceso, oscurece qué recibe de verdad el hijo, así que resérvala para casos claros de reenvío.

Anidar: slots

Las props pasan datos, pero a veces quieres pasar marcado: envolver contenido dentro de un componente. Para eso está el slot, un hueco en la plantilla del hijo donde Astro inyecta lo que el padre escribió entre las etiquetas de apertura y cierre. Es el mecanismo que permite escribir componentes de envoltura —tarjetas, layouts, diálogos— que no saben de antemano qué contendrán.

---
// Panel.astro
interface Props { titulo: string; }
const { titulo } = Astro.props;
---
<section class="panel">
  <h2>{titulo}</h2>
  <slot />
</section>
---
import Panel from '../components/Panel.astro';
---
<Panel titulo="Resumen">
  <p>Este párrafo aterriza en el slot del panel.</p>
</Panel>

Cuando necesitas varios huecos distintos, usas slots con nombre. El hijo declara <slot name="pie" /> y el padre dirige contenido a ese hueco con el atributo slot="pie". Todo lo que no lleve nombre cae en el slot por defecto. Un slot puede además ofrecer contenido de reserva entre sus etiquetas, que se muestra solo si el padre no llena ese hueco.

<!-- en el hijo -->
<footer><slot name="pie">Sin pie definido</slot></footer>

<!-- en el padre -->
<Panel titulo="Ventas">
  <p>Contenido principal.</p>
  <span slot="pie">Actualizado hoy</span>
</Panel>

El árbol de composición

Componer produce un árbol: una página usa un layout, el layout usa una cabecera y un pie, la cabecera usa un logo. Astro recorre ese árbol de arriba abajo, renderiza cada nodo a su HTML, encaja las props y los slots, y aplana todo en un único documento. No queda ninguna huella de la estructura de componentes en la salida: donde tú viste Tarjeta, el navegador ve un article.

flowchart TB
PAGE[pagina .astro] --> LAYOUT[Layout .astro]
LAYOUT --> HEAD[Cabecera .astro]
LAYOUT --> CARD[Tarjeta .astro]
CARD --> SLOT[contenido anidado via slot]
HEAD --> TREE[un unico arbol HTML aplanado]
SLOT --> TREE
style PAGE fill:#89b4fa,color:#11111b
style TREE fill:#a6e3a1,color:#11111b

Que el árbol se aplane en el build tiene un efecto secundario agradable: el HTML final es limpio. No aparecen envoltorios artificiales, ni atributos de framework, ni marcadores de componente; solo las etiquetas semánticas que escribiste dentro de cada pieza. El navegador —y también el motor de búsqueda— reciben un documento que parece escrito a mano, aunque lo compusieras con decenas de componentes anidados.

ℹ️
Astro.slots para saber si un hueco viene lleno

Dentro del hijo puedes consultar Astro.slots.has('pie') para saber si el padre proporcionó contenido a un slot con nombre, y renderizar la estructura circundante solo cuando lo hizo. Es la forma de evitar cabeceras o pies vacíos cuando el slot es opcional, y demuestra que los slots no son huecos pasivos: la valla puede razonar sobre ellos antes de decidir el marcado.

Componer sin coste es una forma distinta de abstraer

En los frameworks que corren en el cliente, cada capa de componentes que añades tiene un precio que se paga en el navegador: más nodos en el árbol virtual, más trabajo de reconciliación, más código que descargar y ejecutar. Eso crea una tensión silenciosa en el diseño: sabes que descomponer en muchos componentes pequeños es más limpio, pero una parte de ti recela porque cada abstracción cuesta milisegundos de runtime al usuario. Astro disuelve esa tensión de raíz. Como el árbol entero se aplana a HTML en el build, componer con diez capas o con una produce exactamente la misma salida y el mismo coste de cliente: cero. La abstracción se vuelve, por fin, gratis en el sentido que siempre prometió la buena ingeniería y casi nunca cumplió: puedes factorizar tu interfaz en las piezas que mejor expresen tu intención —un componente por cada concepto, por diminuto que sea— sin que el usuario pague por tu claridad. Esto reordena las prioridades del diseño de componentes. Dejas de optimizar por número de componentes o profundidad del árbol y empiezas a optimizar solo por legibilidad y reutilización, que es donde la descomposición debía haber apuntado siempre. Un slot deja de ser un mecanismo que quizá encarezca el render y pasa a ser pura organización del marcado. Las props dejan de arrastrar la sospecha de provocar repintados y se vuelven simples parámetros de una función que devuelve HTML. En el fondo, componer en Astro se parece más a componer funciones puras que a ensamblar widgets vivos: cada componente es una función de props y slots a marcado, el árbol es su composición matemática, y el resultado se evalúa una vez. Cuando la abstracción no cuesta, la única guía que queda es la buena forma —y esa es exactamente la restricción que uno querría tener.

⚔️ Ensambla un árbol de componentes
  1. Crea Tarjeta.astro con una interface Props que exija titulo y acepte destacado opcional; úsalo dos veces desde una página con props distintas.
  2. Añade un <slot /> a la tarjeta y pásale contenido anidado desde el padre; comprueba dónde aparece en el HTML.
  3. Introduce un slot con nombre pie con contenido de reserva; usa la tarjeta una vez llenando ese slot y otra dejándolo vacío, y contrasta las dos salidas.
  4. Envuelve la página en un Layout.astro que reciba el resto por su slot por defecto, construye el sitio e inspecciona el HTML para confirmar que el árbol de componentes quedó aplanado sin rastro de sus nombres.