Manual Completo de Astro.js con TypeScript

Introducción

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.

¿Por qué Astro + TypeScript?

¿Qué aprenderás en este manual?

Este manual te guiará desde la configuración inicial hasta conceptos avanzados, incluyendo:

Configuración Inicial

Crear un nuevo proyecto

Para comenzar, crea un nuevo proyecto Astro con soporte para TypeScript:

npm create astro@latest -- --template with-typescript

O si prefieres yarn:

yarn create astro --template with-typescript

Estructura del proyecto

Después de la instalación, tendrás una estructura similar a esta:

. ├── src/ │ ├── components/ # Componentes reutilizables │ ├── layouts/ # Plantillas de layout │ ├── pages/ # Archivos de ruta │ └── env.d.ts # Tipos globales ├── public/ # Assets estáticos ├── astro.config.mjs # Configuración de Astro ├── tsconfig.json # Configuración de TypeScript └── package.json # Dependencias y scripts

Configuración de TypeScript

El archivo tsconfig.json viene preconfigurado, pero podemos personalizarlo:

tsconfig.json
{
  "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"]
}
Configuraciones importantes:

Configuración de Astro

El archivo astro.config.mjs controla la configuración de Astro:

astro.config.mjs
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
  integrations: [],
  vite: {
    build: {
      target: 'esnext'
    }
  }
});

Componentes Astro con TypeScript

Los componentes Astro (.astro) pueden usar TypeScript directamente en el frontmatter (la parte entre los guiones ---).

Componente básico con props tipadas

src/components/Button.astro
---
// 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>

Uso del componente

src/pages/index.astro
---
import Button from '../components/Button.astro';
---

<Button 
  text="Haz clic aquí" 
  variant="primary" 
  size="large" 
/>
Tip: Astro proporciona autocompletado de TypeScript para las props cuando importas componentes. Si pasas una prop no definida en la interfaz, TypeScript mostrará un error.

Componentes con slots

Los slots permiten contenido más flexible. Podemos tiparlos también:

src/components/Card.astro
---
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>

Componentes de Framework con TypeScript

Astro soporta componentes de React, Vue, Svelte y otros frameworks con TypeScript.

Configurar React con TypeScript

Primero, instala las dependencias necesarias:

npm install @astrojs/react react react-dom @types/react @types/react-dom

Luego, configura Astro para usar React:

astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  integrations: [react()]
});

Componente React con TypeScript

src/components/Counter.tsx
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>
  );
}

Usar el componente React en Astro

src/pages/index.astro
---
import Counter from '../components/Counter.tsx';
---

<Counter client:load initialValue={5} step={2} />
Directivas de hidratación:

Sistema de Routing

Astro usa un sistema de routing basado en archivos similar a Next.js.

Rutas estáticas

Crea archivos .astro en src/pages para generar rutas:

src/pages/ ├── index.astro → / ├── about.astro → /about ├── blog/ │ ├── index.astro → /blog │ └── post-1.astro → /blog/post-1

Rutas dinámicas

Para rutas dinámicas, usa corchetes:

src/pages/blog/[slug].astro
---
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>

Rutas API

Puedes crear endpoints API con TypeScript:

src/pages/api/users.json.ts
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
};

Obtención de Datos

Astro soporta varias formas de obtener datos, todas con TypeScript.

Fetch en componentes

src/components/Weather.astro
---
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>

Content Collections

Astro 2.0+ introduce Content Collections para manejar contenido markdown/MDX con TypeScript.

src/content/config.ts
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
};
src/pages/blog/[...slug].astro
---
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>

Despliegue en Producción

Build para producción

Astro puede generar sitios estáticos o SSR (Server-Side Rendering).

npm run build

Esto generará tu sitio en el directorio dist/.

Adaptadores para SSR

Para SSR, necesitas un adaptador para tu plataforma de hosting:

npm install @astrojs/vercel
astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel()
});

Adaptadores disponibles

Static (por defecto)

  • GitHub Pages
  • Netlify Static
  • Vercel Static

Server (SSR)

  • @astrojs/node
  • @astrojs/vercel
  • @astrojs/netlify
  • @astrojs/cloudflare

Conclusión

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:

Recursos adicionales: