Cómo tipar props en Astro con TypeScript usando interfaces
Introducción
Si estás empezando a usar Astro y ya te sentís cómodo con sus archivos .astro, seguramente te hayas encontrado con la necesidad de pasar datos entre componentes. En JavaScript puro eso se hace con props, pero sin tipado podés terminar pasando tipos incorrectos, olvidando propiedades o recibiendo undefined donde esperabas un objeto. Acá entra TypeScript: al tipar las props con una interface obtenés autocompletado, detección temprana de errores y documentación viva de lo que cada componente espera.
Este artículo está dirigido a desarrolladores que ya conocen lo básico de Astro (crear páginas, componentes y usar frontend scripts) y quieren llevar su código al siguiente nivel con tipado estático. Al finalizar vas a saber:
- Cómo definir una interfaz de props en un componente Astro.
- Cómo usar esa interfaz tanto en la definición del componente como en su invocador.
- Qué hacer con props opcionales, con valores por defecto y con tipos complejos (uniones, anidados, generics).
- Buenas prácticas y errores comunes que evitán dolores de cabeza.
Preparate para ver ejemplos con su salida esperada, todo explicado en voseo y con un tono profesional pero cercano.
¿Por qué tipar props en Astro?
Astro es un framework que combina lo mejor de los sitios estáticos con la capacidad de usar componentes de React, Svelte, Vue o su propio sintaxis. Cuando usás TypeScript en un proyecto Astro (activado con astro add typescript), cada archivo .ts o .tsx se tipa como cualquier otro proyecto TS. Sin embargo, los componentes .astro también pueden recibir props tipadas si le indicas a Astro cuál es su tipo.
Sin tipado:
- No tenés autocompletado al pasar props.
- Los errores de tipado solo aparecen en tiempo de ejecución (por ejemplo, intentar acceder a una propiedad que no existe).
- La documentación del componente queda implícita y depende de leer su código.
Con una interface de props:
- Tu editor (VS Code, WebStorm, etc.) muestra exactamente qué props están disponibles y su tipo.
- El compilador de TypeScript avisa si pasás una prop de tipo incorrecto o si omitís una requerida.
- Futuros mantenedores (incluyéndote a vos) entienden rápidamente el contrato del componente.
Configurando un proyecto Astro con TypeScript
Si todavía no tenés TypeScript habilitado, ejecutá:
npx astro add typescriptEsto agrega el paquete @astrojs/ts-plugin y actualiza tsconfig.json. Reiniciá el servidor de desarrollo y ya podés usar .ts y .tsx en tu proyecto.
Ahora, los componentes .astro pueden tener un bloque de script con lang="ts" y, lo más importante, pueden exportar una interfaz que describa sus props. Veamos cómo.
Definiendo la interfaz de props
En un componente Astro, la forma estándar de describir sus props es exportar una interface (o un type) llamada Props. Astro la detecta automáticamente y la usa para tipar tanto la definición del componente como su uso.
Ejemplo básico: un componente de tarjeta
Creemos src/components/Card.astro:
---
interface Props {
/** Título de la tarjeta */
title: string;
/** Contenido principal */
content: string;
/** URL opcional de una imagen */
imageUrl?: string;
}
const { title, content, imageUrl } = Astro.props;
---
<div class="card">
{imageUrl && <img src={imageUrl} alt={title} />}
<h2>{title}</h2>
<p>{content}</p>
</div>
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
max-width: 400px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
img { max-width: 100%; border-radius: 4px; }
</style>En este ejemplo:
- La interfaz
Propsdefine tres propiedades:titleycontentson obligatorias;imageUrles opcional (note el?). - Dentro del bloque de frontmatter (
---) desestructuramosAstro.propsy obtenemos los valores tipados. - En el markup usamos esas variables normalmente.
Usando el componente
En cualquier otra página o componente, por ejemplo src/pages/index.astro:
---
import Card from '../components/Card.astro';
---
<Card
title="Mi primera tarjeta"
content="Este es el contenido de la tarjeta demostrativo." />
<Card
title="Tarjeta con imagen"
content="Una imagen mejora la presentación."
imageUrl="https://example.com/img.jpg" />
</Card>Si intentás pasar una prop que no existe o de tipo incorrecto, el editor mostrará un error inmediato y el comando astro build fallará.
Salida esperada en el navegador:
Se renderizarán dos tarjetas: la primera solo con título y contenido; la segunda con una imagen arriba del título. El estilo aplicado dará un aspecto de tarjeta con borde y sombra.
Props opcionales y valores por defecto
A veces querés que una prop tenga un valor por defecto cuando no se provee. TypeScript no permite asignar valores por defecto directamente en la interfaz, pero podés manejarlo en el desestructurado.
Ejemplo: prop de número con default
src/components/Counter.astro:
---
interface Props {
/** Valor inicial del contador */
start?: number;
/** Incremento por paso */
step?: number;
}
const { start = 0, step = 1 } = Astro.props;
---
<p>Contador: <strong>{start}</strong></p>
<button onclick="this.innerText = Number(this.innerText) + {step}" >
Sumar {step}
</button>En este caso:
startystepson opcionales en la interfaz.- Al desestructurar, asignamos valores por defecto (
= 0y= 1). - El botón muestra el incremento actual; al hacer clic se actualiza el texto (esto es solo para demostración, en un caso real usarías un framework).
Uso
<Counter />
<!-- usa start=0, step=1 -->
<Counter start={10} step={5} />
<!-- empieza en 10, incrementa de 5 en 5 -->Si pasás start como una cadena (start=" diez"), TypeScript avisará que no se puede asignar string a number.
Tipos union y literales
Las props pueden aceptar varios tipos específicos mediante uniones (|) o valores literales. Esto es útil para crear APIs más expresivas.
Ejemplo: variante de botón
src/components/Button.astro:
---
interface Props {
/** Texto que se muestra */
label: string;
/** Tipo de botón: 'primary', 'secondary' o 'danger' */
variant: 'primary' | 'secondary' | 'danger';
/** Si está deshabilitado */
disabled?: boolean;
}
const { label, variant, disabled = false } = Astro.props;
---
<button
class={`btn btn-${variant} ${disabled ? 'disabled' : ''}`}
disabled={disabled}
>
{label}
</button>
<style>
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
}
.btn-primary { background: #0066ff; color: white; }
.btn-secondary { background: #6c757d; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn.disabled { opacity: 0.5; cursor: not-allowed; }
</style>Al usar el componente:
<Button label="Guardar" variant="primary" />
<Button label="Eliminar" variant="danger" disabled />Si intentás pasar variant="tertiary" el editor mostrará error, porque solo se permiten los tres literales definidos.
Props anidados (objetos complejos)
Cuando la prop es un objeto con varias sub‑propiedades, lo mejor es definir una interfaz para ese objeto y usarla como tipo de la prop principal.
Ejemplo: componente de usuario con perfil
src/components/UserProfile.astro:
---
interface Profile {
avatarUrl: string;
bio: string;
location?: string;
}
interface Props {
/** Nombre completo del usuario */
name: string;
/** Edad en años */
age: number;
/** Información extra del perfil */
profile: Profile;
}
const { name, age, profile } = Astro.props;
---
<div class="user-card">
<img src={profile.avatarUrl} alt={name} />
<h2>{name}</h2>
<p>Edad: {age}</p>
<p>{profile.bio}</p>
{profile.location && <p>📍 {profile.location}</p>}
</div>
<style>
.user-card { text-align: center; padding: 1rem; }
img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin-bottom: 0.5rem; }
</style>Uso
<UserProfile
name="Ana López"
age={28}
profile={{
avatarUrl: 'https://i.pravatar.cc/150?img=3',
bio: 'Desarrolladora full stack amante del código abierto.',
location: 'Buenos Aires, Argentina'
}}
/>Salida esperada:
Una tarjeta centrada con una imagen redonda, el nombre, la edad, la bio y, como location está provisto, una línea con el ícono de ubicación y la ciudad.
Si intentás pasar profile sin avatarUrl o con un tipo incorrecto (por ejemplo, avatarUrl: 123), TypeScript lo detectará antes de que el código se compile.
Props con generics
Aunque menos frecuente, hay casos en los que querés que un componente sea genérico respecto al tipo de ciertos datos que recibe. Por ejemplo, un componente que muestra una lista y permite pasar la función de renderizado de cada ítem.
Ejemplo: lista genérica
src/components/GenericList.astro:
---
interface Props<T> {
/** Arreglo de ítems */
items: T[];
/** Función que recibe un ítem y devuelve su representación JSX */
renderItem: (item: T) => JSX.Element;
}
const { items, renderItem } = Astro.props;
---
<ul>
{items.map((item, idx) => (
<li key={idx}>{renderItem(item)}</li>
))}
</ul>
<style>
ul { list-style: none; padding: 0; }
li { padding: 0.5rem 0; border-bottom: 1px solid #eee; }
li:last-child { border-bottom: none; }
</style>Uso con diferentes tipos
---
import GenericList from '../components/GenericList.astro';
const numbers = [1, 2, 3, 4, 5];
const fruits = ['manzana', 'banana', 'naranja'];
---
<h3>Lista de números</h3>
<GenericList
items={numbers}
renderItem={(n) => <span>{n * 10}</span>}
/>
<h3>Lista de frutas</h3>
<GenericList
items={fruits}
renderItem={(f) => <strong>{f.toUpperCase()}</strong>}
/>Salida esperada:
- En la primera lista, cada número se muestra multiplicado por 10 (20, 30, …).
- En la segunda, cada fruta se muestra en mayúsculas y en negrita.
Si intentás pasar un renderItem que no coincida con el tipo de items (por ejemplo, darle una función que espera string cuando items es number[]), TypeScript mostrará un error.
Buenas prácticas al tipar props en Astro
- Nombrá la interfaz
Props– Astro la busca por convención; si usás otro nombre tenés que indicarlo explícitamente conexport interface MiPropsy luego usarAstro.props as MiProps, pero lo más simple es seguir la norma. - Separá lo esencial de lo opcional – Poné primero las props requeridas y después las opcionales; eso mejora la legibilidad y ayuda a quien lea el componente.
- Usá comentarios JSDoc – Dentro de la interfaz,
/** … */aparece en el autocompletado de tu editor, brindando contexto inmediato. - Evité el mayor que sea posible – Si necesitás flexibilidad, prefirá
unknowny luego untype guardoascontrolado, nuncaany. - Aprovechá los literales y uniones – Definí conjuntos cerrados de valores (como variantes de botón) para evitar errores de escritura.
- No olvides los valores por defecto – Si una prop es opcional pero tenés un sentido razonable de default, asignalo al desestructurar.
- Mantení las interfaces cerca del componente – Si la interfaz solo se usa en ese archivo, declárala dentro del mismo
.astro. Si se reutiliza en varios componentes, movéla a un archivotypes.tsy exportala. - Validá en tiempo de build – Ejecutá
astro buildseguido deastro previewpara asegurarte de que no haya errores de tipado que solo aparezcan en producción. - Documentá ejemplos de uso – En el mismo archivo, bajo un comentario, poné un pequeño bloque de ejemplo de cómo se invoca el componente; eso ayuda a futuros mantenedores.
- Revisá la salida del linter – Si usás ESLint con el plugin
@typescript-eslint, activá reglas como@typescript-eslint/explicit-module-boundary-typesy@typescript-eslint/no-explicit-anypara mantener el código limpio.
Errores comunes y cómo evitarlos
1. Olvidar exportar la interfaz
Si definís la interfaz pero no la exportás (interface Props { ... } sin export), Astro no la reconocerá y las props quedarán con tipo any.
Solución: siempre usar export interface Props { ... }.
2. Usar any en la desestructuración
Algunos escribís const { title, content } = Astro.props as any; para evitar errores de TypeScript. Esto anula todo el beneficio del tipado.
Solución: confía en la interfaz y dejá que TypeScript infiera el tipo; si necesitás un cast, hacelo solo cuando sea absolutamente necesario y documentá por qué.
3. Confundir opcional con undefined
Una prop opcional (imageUrl?: string) puede recibir undefined explícitamente o simplemente no pasarse. Ambas situaciones dan undefined en tiempo de ejecución, pero la diferencia semántica puede importar si más adelante querés distinguir entre "no provisto" y "provisto como undefined" (raro, pero posible).
Solución: si necesitás saber si la prop fue provista, usá un patrón como:
const { imageUrl } = Astro.props;
const hasImage = Object.hasOwn(Astro.props, 'imageUrl');4. Pasar props con nombres incorrectos por case‑sensitivity
TypeScript distingue mayúsculas y minúsculas. Si en la interfaz tenés userId y en la invocación escribís userid, el compilador avisará.
Solución: prestá atención al autocompletado; si el editor no sugiere la prop, probablemente esté mal escrita.
5. No actualizar la interfaz cuando cambias el componente
Si agregás una nueva prop requerida al componente pero olvidás agregarla a la interfaz, TypeScript seguirá pensando que la prop es opcional (o inexistente) y no avisará si alguien la omite.
Solución: cada vez que modifiques el componente, revisá la interfaz. Una buena práctica es escribir la interfaz primero y luego implementar el componente basado en ella.
Conclusión
Tipar las props en Astro con TypeScript usando una interface no es solo una cuestión de formalidad; es una inversión en la mantenibilidad y la robustez de tus aplicaciones. Al seguir los pasos mostrados — definir una interfaz clara, exportarla, desestructurar con posibles valores por defecto y usar tipos avanzados como uniones, literales, objetos anidados y generics — obtenés:
- Autocompletado preciso en tu editor.
- Detección temprana de errores antes de que el código llegue al navegador.
- Documentación viva que se actualiza automáticamente cuando cambiás la interfaz.
- Mayor confianza al reutilizar componentes en distintos proyectos o al compartir código con tu equipo.
Recordá siempre exportar la interfaz como Props, usar comentarios JSDoc para explicar cada campo y aplicar valores por defecto al desestructurar cuando corresponda. Evitá los atajos como any y prestá atención a los errores que TypeScript te muestra; son tus mejores aliados para mantener un código limpio y predecible.
Con estos conceptos ya podés comenzar a crear bibliotecas de componentes tipados en Astro, mejorar la experiencia de desarrollo de tus sitios y reducir los bugs en producción. ¡Manos al código y a seguir construyendo interfaces más seguras!
¿Te quedó alguna duda o querés ver algún caso de uso más avanzado? Dejá un comentario abajo y seguimos la conversación.
Próximos pasos: explorá cómo combinar estas tipadas con los props con los collections de Astro para tipar el contenido de Markdown, o cómo usar Zod para validar props en tiempo de ejecución cuando los datos vienen de fuentes externas.
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.