Astro.js es un framework web moderno que te permite construir sitios más rápidos con menos JavaScript. Cuando lo combinamos con TypeScript, obtenemos todos los beneficios de un sistema de tipos estático para mejorar la calidad y mantenibilidad de nuestro código.
Este manual te guiará desde la configuración inicial hasta conceptos avanzados, incluyendo:
Para comenzar, crea un nuevo proyecto Astro con soporte para TypeScript:
O si prefieres yarn:
Después de la instalación, tendrás una estructura similar a esta:
El archivo tsconfig.json
viene preconfigurado, pero podemos personalizarlo:
{ "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "jsxImportSource": "astro", "baseUrl": ".", "paths": { "@/*": ["src/*"] }, "types": ["astro/client"] }, "include": ["src/**/*", "astro.config.mjs"], "exclude": ["node_modules"] }
"strict": true
- Habilita todas las verificaciones de tipo estrictas"jsxImportSource": "astro"
- Necesario para soporte JSX en Astro"types": ["astro/client"]
- Incluye tipos de Astro globalmenteEl archivo astro.config.mjs
controla la configuración de Astro:
import { defineConfig } from 'astro/config'; // https://astro.build/config export default defineConfig({ integrations: [], vite: { build: { target: 'esnext' } } });
Los componentes Astro (.astro) pueden usar TypeScript directamente en el frontmatter (la parte entre los guiones ---).
--- // Definimos la interfaz para las props interface ButtonProps { text: string; variant?: 'primary' | 'secondary' | 'outline'; size?: 'small' | 'medium' | 'large'; onClick?: () => void; } // Extraemos las props con valores por defecto const { text, variant = 'primary', size = 'medium', onClick } = Astro.props; --- <button class={`btn ${variant} ${size}`} on:click={onClick} > {text} </button> <style> .btn { border-radius: 0.375rem; font-weight: 500; transition: all 0.2s; } .primary { background-color: var(--primary); color: white; } .secondary { background-color: #e2e8f0; color: #1e293b; } .outline { background-color: transparent; border: 1px solid var(--primary); color: var(--primary); } .small { padding: 0.25rem 0.5rem; font-size: 0.875rem; } .medium { padding: 0.5rem 1rem; font-size: 1rem; } .large { padding: 0.75rem 1.5rem; font-size: 1.125rem; } </style>
--- import Button from '../components/Button.astro'; --- <Button text="Haz clic aquí" variant="primary" size="large" />
Los slots permiten contenido más flexible. Podemos tiparlos también:
--- interface CardProps { title: string; padding?: 'none' | 'small' | 'medium' | 'large'; } const { title, padding = 'medium' } = Astro.props; --- <article class={`card ${padding}`}> <h3 class="card-title">{title}</h3> <div class="card-content"> <slot /> </div> </article> <style> .card { border-radius: 0.5rem; border: 1px solid #e2e8f0; background-color: white; } .card-title { font-size: 1.25rem; font-weight: 600; padding: 1rem 1rem 0; margin: 0; } .card-content { padding: 1rem; } .none { padding: 0; } .small { padding: 0.5rem; } .medium { padding: 1rem; } .large { padding: 1.5rem; } </style>
Astro soporta componentes de React, Vue, Svelte y otros frameworks con TypeScript.
Primero, instala las dependencias necesarias:
Luego, configura Astro para usar React:
import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; export default defineConfig({ integrations: [react()] });
import { useState } from 'react'; interface CounterProps { initialValue?: number; step?: number; } export default function Counter({ initialValue = 0, step = 1 }: CounterProps) { const [count, setCount] = useState(initialValue); return ( <div className="counter"> <p>Current count: {count}</p> <button onClick={() => setCount(count + step)}> Increment </button> <button onClick={() => setCount(count - step)}> Decrement </button> </div> ); }
--- import Counter from '../components/Counter.tsx'; --- <Counter client:load initialValue={5} step={2} />
client:load
- Carga e hidrata inmediatamenteclient:idle
- Hidrata cuando el navegador está inactivoclient:visible
- Hidrata cuando el componente es visibleclient:media
- Hidrata en ciertos breakpointsAstro usa un sistema de routing basado en archivos similar a Next.js.
Crea archivos .astro en src/pages
para generar rutas:
Para rutas dinámicas, usa corchetes:
--- import type { GetStaticPaths } from 'astro'; interface Post { slug: string; title: string; content: string; date: string; } // Simulamos datos de posts const posts: Post[] = [ { slug: 'primer-post', title: 'Mi primer post', content: 'Contenido del primer post...', date: '2023-01-01' }, { slug: 'segundo-post', title: 'Mi segundo post', content: 'Contenido del segundo post...', date: '2023-01-15' } ]; // Generamos las rutas estáticas export const getStaticPaths: GetStaticPaths = async () => { return posts.map(post => ({ params: { slug: post.slug }, props: { post } })); }; // Tipamos las props interface Props { post: Post; } const { post } = Astro.props; --- <article> <h1>{post.title}</h1> <time datetime={post.date}>{new Date(post.date).toLocaleDateString()}</time> <p>{post.content}</p> </article>
Puedes crear endpoints API con TypeScript:
import type { APIRoute } from 'astro'; interface User { id: number; name: string; email: string; } const users: User[] = [ { id: 1, name: 'Juan Pérez', email: 'juan@example.com' }, { id: 2, name: 'María García', email: 'maria@example.com' } ]; export const get: APIRoute = async () => { return new Response(JSON.stringify(users), { headers: { 'Content-Type': 'application/json' } }); }; // También podemos manejar otros métodos HTTP export const post: APIRoute = async ({ request }) => { // Lógica para manejar POST };
Astro soporta varias formas de obtener datos, todas con TypeScript.
--- interface WeatherData { location: { name: string; region: string; }; current: { temp_c: number; condition: { text: string; icon: string; }; }; } const response = await fetch('https://api.weatherapi.com/v1/current.json?key=TU_API_KEY&q=Madrid'); const data: WeatherData = await response.json(); --- <div class="weather"> <h3>Clima en {data.location.name}</h3> <p>Temperatura: {data.current.temp_c}°C</p> <p>Condición: {data.current.condition.text}</p> </div>
Astro 2.0+ introduce Content Collections para manejar contenido markdown/MDX con TypeScript.
import { defineCollection, z } from 'astro:content'; const blogCollection = defineCollection({ schema: z.object({ title: z.string(), description: z.string().optional(), pubDate: z.date(), author: z.string().default('Anónimo'), tags: z.array(z.string()).optional(), draft: z.boolean().default(false) }) }); export const collections = { 'blog': blogCollection };
--- import { getCollection } from 'astro:content'; import BlogLayout from '../../layouts/BlogLayout.astro'; export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { slug: post.slug }, props: { post } })); } interface Props { post: { data: { title: string; pubDate: Date; author: string; }; body: string; }; } const { post } = Astro.props; --- <BlogLayout title={post.data.title}> <h1>{post.data.title}</h1> <p>Por {post.data.author}</p> <time datetime={post.data.pubDate.toISOString()}> {post.data.pubDate.toLocaleDateString()} </time> <article> <Content /> </article> </BlogLayout>
Astro puede generar sitios estáticos o SSR (Server-Side Rendering).
Esto generará tu sitio en el directorio dist/
.
Para SSR, necesitas un adaptador para tu plataforma de hosting:
import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ output: 'server', adapter: vercel() });
Astro.js con TypeScript ofrece una combinación poderosa para construir sitios web modernos, rápidos y mantenibles. A lo largo de este manual hemos cubierto: