Astro: Construyendo Sitios Web Ultrarrápidos con Islands Architecture
Astro ha revolucionado la forma de construir sitios web orientados a contenido. Su filosofía es radical: envía cero JavaScript al navegador por defecto. Solo añade JS donde realmente lo necesitas, a través del concepto de "islas de interactividad".
Este portfolio que estás leyendo está construido con Astro, y en este artículo te enseño cómo funciona todo.
¿Por qué Astro?
El problema de los SPAs para contenido
Si construyes un blog con React, Vue o Angular, el navegador tiene que:
- Descargar el HTML (casi vacío).
- Descargar y parsear React (100KB+).
- Descargar y ejecutar tu código.
- Renderizar el contenido.
¿Todo eso para mostrar un artículo de texto? Astro resuelve esto generando HTML estático en el servidor y solo enviando JavaScript donde sea necesario.
Comparativa de JavaScript enviado al navegador
| Framework | JS para un blog post |
|---|---|
| Next.js (React) | ~80-150KB |
| Nuxt (Vue) | ~60-120KB |
| SvelteKit | ~30-60KB |
| Astro | ~0KB (sin islas) |
Estructura de un proyecto Astro
src/
├── components/ # Componentes .astro
│ ├── Header.astro
│ ├── Footer.astro
│ └── Card.astro
├── layouts/ # Layouts reutilizables
│ └── MainLayout.astro
├── pages/ # Rutas basadas en archivos
│ ├── index.astro # → /
│ ├── about.astro # → /about
│ └── blog/
│ ├── index.astro # → /blog
│ └── [slug].astro # → /blog/mi-post
├── styles/
│ └── global.css
public/ # Assets estáticos (sin procesar)
Componentes Astro: Lo básico
Un componente .astro tiene dos partes: un frontmatter (JavaScript del servidor) y un template (HTML).
---
// Esto se ejecuta en BUILD TIME, no en el navegador
import Header from '../components/Header.astro';
interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
const publishedDate = new Date().toISOString();
// Puedes hacer fetch a APIs aquí
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---
<html lang="es">
<head>
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<Header />
<h1>{title}</h1>
<p>Publicado: {publishedDate}</p>
<slot /> <!-- Equivalente a children en React -->
</body>
</html>
Clave: Todo el JavaScript del frontmatter se ejecuta en el servidor durante el build. Cero JS llega al navegador.
Islands Architecture: Islas de Interactividad
La magia de Astro está en poder usar React, Vue, Svelte o Solid solo donde necesites interactividad:
---
import StaticHeader from '../components/Header.astro'; // Sin JS
import ReactCounter from '../components/Counter.jsx'; // Con JS
import VueSearch from '../components/Search.vue'; // Con JS
---
<!-- El header es HTML puro, 0 JS -->
<StaticHeader />
<main>
<h1>Mi página</h1>
<p>Este contenido es HTML estático</p>
<!-- Esta "isla" carga React solo para este componente -->
<ReactCounter client:visible />
<!-- Esta isla carga Vue solo cuando el usuario interactúa -->
<VueSearch client:idle />
</main>
Directivas client:*
| Directiva | Cuándo se hidrata | Caso de uso |
|---|---|---|
client:load |
Inmediatamente | Elementos above the fold interactivos |
client:idle |
Cuando el navegador está idle | Componentes no críticos |
client:visible |
Cuando entra en viewport | Componentes below the fold |
client:media |
Cuando se cumple un media query | Componentes responsive |
client:only |
Solo cliente (sin SSR) | Componentes que usan APIs del navegador |
Content Collections: Contenido tipado
Astro tiene un sistema integrado para manejar colecciones de contenido (como posts de blog) con validación de esquema:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
tags: z.array(z.string()),
image: z.string().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
Luego creas archivos Markdown/MDX en src/content/blog/:
---
title: "Mi primer post"
description: "Una introducción a Astro"
pubDate: 2025-01-15
tags: ["astro", "web"]
---
# Mi primer post
Contenido del post aquí...
Y los consultas con tipos:
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);
const sortedPosts = posts.sort((a, b) => b.data.pubDate - a.data.pubDate);
---
{sortedPosts.map(post => (
<article>
<h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
<time>{post.data.pubDate.toLocaleDateString()}</time>
</article>
))}
View Transitions
Astro incluye View Transitions nativas que crean transiciones fluidas entre páginas sin necesidad de un SPA:
---
import { ClientRouter } from 'astro:transitions';
---
<html>
<head>
<ClientRouter />
</head>
<body>
<h1 transition:name="title">Mi Título</h1>
<img transition:name="hero" src="/hero.jpg" />
</body>
</html>
Al navegar entre páginas, los elementos con el mismo transition:name se animan suavemente de una posición a otra. Es la misma tecnología que usa este blog.
Conclusión
Astro es la elección perfecta cuando tu prioridad es rendimiento y contenido. No necesitas sacrificar la experiencia del desarrollador: puedes usar tu framework favorito como islas, tienes tipado con TypeScript, y las View Transitions dan una experiencia de SPA sin el coste de JavaScript. Si estás construyendo un blog, portfolio, documentación o landing page, Astro debería ser tu primera opción.