Astro Framework
Guía completa del framework Astro para sitios web de alto rendimiento. Cubre la Islands Architecture, componentes, layouts, Content Collections e integraciones con frameworks como React, Vue y Svelte.
Fundamentals
Astro es un framework content-first que genera HTML estático por defecto, cargando JavaScript solo cuando es estrictamente necesario. El resultado es sitios extremadamente rápidos con cero JS por defecto.
Estructura del proyecto
Estructura estándar de un proyecto Astro
astro-project/
├── public/ # Assets estáticos (copiados tal cual)
│ ├── favicon.svg
│ └── images/
├── src/
│ ├── components/ # Componentes .astro, .tsx, .vue
│ ├── layouts/ # Layouts reutilizables
│ ├── pages/ # File-based routing
│ ├── styles/ # CSS global
│ ├── content/ # Content Collections (Markdown/MDX)
│ └── data/ # Data files (.ts, .json)
├── astro.config.mjs # Configuración principal
├── tsconfig.json
└── package.json Archivo de componente .astro
---
// Frontmatter: JavaScript del servidor (se ejecuta en build-time)
interface Props {
title: string;
variant?: 'primary' | 'secondary';
}
const { title, variant = 'primary' } = Astro.props;
const isExternal = title.startsWith('http');
---
<!-- Template: HTML que se renderiza -->
<div class={`card card--${variant}`}>
<h2>{title}</h2>
<slot /> <!-- Contenido hijo -->
</div>
<style>
/* Estilos scoped por defecto — no afectan otros componentes */
.card { padding: 1rem; border-radius: 8px; }
.card--primary { background: var(--bg-surface); }
.card--secondary { background: var(--bg-void); }
</style> Zero JavaScript por defecto
A diferencia de React/Next.js, Astro no envía JavaScript al cliente
a menos que uses la directiva client:*. El HTML se genera en build-time
y se sirve como estático.
Islands Architecture
Las Islands (islas) son componentes interactivos que se hidratan independientemente en una página de HTML estático. Cada isla carga su propio JavaScript solo cuando lo necesita.
Islands Architecture — Hidratación selectiva
┌──────────────────────────────────────────────────────────┐
│ Página HTML estática │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Header (HTML puro — zero JS) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌─────────────────────────────┐ │
│ │ Island │ │ Artículo (HTML puro) │ │
│ │ Search │ │ │ │
│ │ (React) │ │ Texto estático, imágenes, │ │
│ │ JS: 12kb │ │ sin ningún JavaScript │ │
│ └──────────┘ └─────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Footer (HTML puro — zero JS) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ Island Newsletter │ (Svelte, JS: 4kb, client:visible) │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────────┘ Directivas de hidratación
| Directiva | Comportamiento | Caso de uso |
|---|---|---|
client:load | Se hidrata inmediatamente al cargar la página | Componentes críticos visibles al inicio |
client:idle | Se hidrata cuando el navegador está en idle | Componentes no urgentes |
client:visible | Se hidrata al entrar en el viewport | Componentes bajo el fold |
client:media | Se hidrata al cumplir un media query | Componentes solo desktop |
client:only | No SSR, solo client-side render | Componentes que usan APIs del browser |
---
// Importar componentes de diferentes frameworks
import SearchBar from '../components/SearchBar.tsx';
import Newsletter from '../components/Newsletter.svelte';
---
<!-- Se hidrata inmediatamente — el buscador es crítico -->
<SearchBar client:load />
<!-- Se hidrata al ser visible — no urgente -->
<Newsletter client:visible />
<!-- Solo en desktop -->
<DesktopSidebar client:media="(min-width: 1024px)" /> Components & Layouts
Los Layouts en Astro son componentes especiales que envuelven páginas con estructura HTML compartida (head, nav, footer). Usan <slot /> para inyectar el contenido de cada página.
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'Documentation site' } = Astro.props;
---
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content={description} />
<title>{title}</title>
<slot name="head" /> <!-- Named slot para scripts/css extra -->
</head>
<body>
<Nav />
<main>
<slot /> <!-- Default slot — contenido de la página -->
</main>
<Footer />
</body>
</html> Named slots
Usa <slot name="head" /> en el layout y
<Fragment slot="head">...</Fragment> en la página
para inyectar código en secciones específicas del layout.
Content Collections
Las Content Collections permiten gestionar contenido Markdown/MDX con validación de schema (Zod), tipado TypeScript y queries tipadas.
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const docs = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
category: z.enum(['guide', 'reference', 'tutorial']),
order: z.number(),
draft: z.boolean().default(false),
publishedAt: z.date(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { docs }; ---
// src/pages/docs/[slug].astro — Página dinámica para cada doc
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const docs = await getCollection('docs', ({ data }) => !data.draft);
return docs.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content, headings } = await entry.render();
---
<BaseLayout title={entry.data.title}>
<article>
<h1>{entry.data.title}</h1>
<Content />
</article>
</BaseLayout> Integrations
Astro soporta integraciones oficiales y de la comunidad para añadir funcionalidad: frameworks UI, adaptadores SSR, optimizaciones de imagen y más.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap';
import node from '@astrojs/node';
export default defineConfig({
site: 'https://docs.example.com',
integrations: [
react(), // Soporte para componentes .tsx
sitemap(), // Genera sitemap.xml automático
],
output: 'hybrid', // static por defecto, con opt-in SSR
adapter: node({
mode: 'standalone',
}),
}); | Integración | Comando | Función |
|---|---|---|
| React | npx astro add react | Componentes .tsx interactivos |
| Vue | npx astro add vue | Componentes .vue |
| Tailwind | npx astro add tailwind | Utility-first CSS |
| MDX | npx astro add mdx | Markdown + JSX |
| Sitemap | npx astro add sitemap | SEO sitemap.xml |
| Node | npx astro add node | SSR adapter para Node.js |
npx astro add
npx astro add <integration> instala automáticamente el paquete
y actualiza astro.config.mjs. No necesitas configurar nada manualmente.
View Transitions
Las View Transitions de Astro permiten crear transiciones fluidas entre páginas sin necesidad de un framework SPA. El navegador anima el cambio de página de forma nativa, manteniendo el rendimiento de un sitio estático con la experiencia de una aplicación de página única.
Activar View Transitions en el layout
Para habilitar las transiciones, importa el componente <ViewTransitions /> y colócalo dentro del <head> de tu layout principal. Todas las páginas que usen ese layout tendrán transiciones automáticas.
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
interface Props {
title: string;
description?: string;
}
const { title, description = 'Documentación' } = Astro.props;
---
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content={description} />
<title>{title}</title>
<!-- Activa las View Transitions en todas las páginas del layout -->
<ViewTransitions />
</head>
<body>
<nav><!-- navegación persistente --></nav>
<main>
<slot />
</main>
</body>
</html> Transiciones con nombre (element morphing)
Usa la directiva transition:name para que Astro identifique elementos equivalentes entre dos páginas y cree una animación de transformación (morph) entre ellos. El valor debe ser único dentro de cada página.
---
// src/pages/blog/index.astro — Listado de artículos
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---
<BaseLayout title="Blog">
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>
<!-- El nombre vincula este elemento con su equivalente en la página de detalle -->
<img
src={post.data.cover}
alt={post.data.title}
transition:name={`cover-${post.slug}`}
/>
<h2 transition:name={`title-${post.slug}`}>
{post.data.title}
</h2>
</a>
</li>
))}
</ul>
</BaseLayout> ---
// src/pages/blog/[slug].astro — Detalle del artículo
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<BaseLayout title={post.data.title}>
<article>
<!-- Mismos transition:name que en el listado — Astro anima entre ambos -->
<img
src={post.data.cover}
alt={post.data.title}
transition:name={`cover-${post.slug}`}
/>
<h1 transition:name={`title-${post.slug}`}>
{post.data.title}
</h1>
<Content />
</article>
</BaseLayout> Directivas de animación
La directiva transition:animate controla el tipo de animación que Astro aplica a un elemento durante la transición de página.
| Directiva | Efecto | Uso recomendado |
|---|---|---|
transition:animate="fade" | Fundido cruzado (opacidad) | Contenido general, imágenes de fondo |
transition:animate="slide" | Deslizamiento lateral con fade | Paneles laterales, navegación paso a paso |
transition:animate="initial" | Sin animación especial (reflow normal) | Elementos que no deben animarse |
transition:animate="none" | Desactiva la transición en el elemento | Elementos que deben permanecer estáticos |
<!-- El sidebar se desliza, el contenido hace fade -->
<aside transition:animate="slide">
<TableOfContents />
</aside>
<article transition:animate="fade">
<slot />
</article>
<!-- El header no se anima — permanece estable -->
<header transition:animate="none">
<Nav />
</header> Persistir islas entre navegaciones
Cuando un componente interactivo (isla) debe mantener su estado entre navegaciones (por ejemplo, un reproductor de audio o un chat), usa transition:persist. Astro reutiliza la instancia del componente en lugar de destruirla y recrearla.
---
import AudioPlayer from '../components/AudioPlayer.tsx';
import ChatWidget from '../components/ChatWidget.svelte';
---
<!-- El reproductor mantiene la canción actual al navegar entre páginas -->
<AudioPlayer client:load transition:persist />
<!-- El chat conserva el historial de mensajes -->
<ChatWidget client:load transition:persist /> Eventos del ciclo de vida
Astro expone eventos que permiten ejecutar código en momentos específicos de la transición. Son útiles para animaciones personalizadas, limpieza de estado o analytics.
| Evento | Momento | Caso de uso |
|---|---|---|
astro:before-preparation | Antes de cargar el contenido de la nueva página | Mostrar indicador de carga, cancelar navegación |
astro:after-preparation | Después de cargar el contenido pero antes de intercambiarlo | Modificar el DOM de la nueva página antes de mostrarla |
astro:before-swap | Justo antes de reemplazar el DOM antiguo con el nuevo | Animaciones de salida personalizadas, guardar estado del scroll |
astro:after-swap | Después de reemplazar el DOM, antes de que la transición finalice | Reinicializar librerías de terceros, restaurar estado |
astro:page-load | La página nueva está completamente cargada y visible | Analytics de página vista, inicializar widgets |
<script>
// Registrar una página vista cada vez que se completa una transición
document.addEventListener('astro:page-load', () => {
analytics.track('page_view', { url: window.location.pathname });
});
// Mostrar una barra de progreso durante la carga
document.addEventListener('astro:before-preparation', () => {
document.getElementById('progress-bar')?.classList.add('loading');
});
// Ocultar la barra cuando el DOM ya se intercambió
document.addEventListener('astro:after-swap', () => {
document.getElementById('progress-bar')?.classList.remove('loading');
});
</script> Comparativa: con y sin View Transitions
| Aspecto | Sin View Transitions | Con View Transitions |
|---|---|---|
| Navegación entre páginas | Recarga completa del navegador (flash blanco) | Transición fluida animada sin recarga visible |
| Estado de islas | Se destruyen y recrean en cada navegación | Persistentes con transition:persist |
| Animaciones entre páginas | No disponibles de forma nativa | Fade, slide y morph de elementos con nombre |
| JavaScript adicional | Ninguno | Router ligero de Astro (~3 KB) |
| Compatibilidad navegadores | Universal | Fallback automático en navegadores sin soporte |
| SEO | Sin cambios | Sin impacto — sigue siendo HTML estático |
| Experiencia percibida | Sitio web estático tradicional | Sensación de aplicación SPA |
Fallback automático
En navegadores que no soportan la View Transitions API nativa (como Firefox a fecha de 2025), Astro aplica un fallback que replica el comportamiento usando animaciones CSS. No necesitas código adicional para manejar la compatibilidad.
SEO & Performance
Astro genera HTML estático por defecto, lo que proporciona una base excelente para SEO. Sin embargo, una configuración adecuada de meta tags, imágenes optimizadas y estrategias de prefetching marcan la diferencia entre un sitio rápido y uno que domina los resultados de búsqueda.
Componente SEO reutilizable
Centraliza toda la gestión de meta tags en un único componente que incluyes en el layout. Esto garantiza consistencia y facilita el mantenimiento.
---
// src/components/SEO.astro
interface Props {
title: string;
description: string;
canonical?: string;
image?: string;
type?: 'website' | 'article';
publishedDate?: string;
noindex?: boolean;
}
const {
title,
description,
canonical = Astro.url.href,
image = '/images/og-default.jpg',
type = 'website',
publishedDate,
noindex = false,
} = Astro.props;
const siteName = 'Mi Documentación';
const fullImageUrl = new URL(image, Astro.site).href;
---
<!-- Meta tags básicos -->
<title>{title} | {siteName}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
{noindex && <meta name="robots" content="noindex, nofollow" />}
<!-- Open Graph (Facebook, LinkedIn, WhatsApp) -->
<meta property="og:type" content={type} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={fullImageUrl} />
<meta property="og:url" content={canonical} />
<meta property="og:site_name" content={siteName} />
{publishedDate && <meta property="article:published_time" content={publishedDate} />}
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={fullImageUrl} /> ---
// src/layouts/BaseLayout.astro — Uso del componente SEO
import SEO from '../components/SEO.astro';
import { ViewTransitions } from 'astro:transitions';
const { title, description, image } = Astro.props;
---
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<SEO title={title} description={description} image={image} />
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html> URLs canónicas y robots.txt
La URL canónica evita contenido duplicado cuando una misma página es accesible desde múltiples URLs. El archivo robots.txt indica a los crawlers qué secciones indexar.
# public/robots.txt
User-agent: *
Allow: /
# Bloquear rutas que no deben indexarse
Disallow: /admin/
Disallow: /api/
Disallow: /preview/
# Ubicación del sitemap
Sitemap: https://docs.example.com/sitemap-index.xml Generación automática de sitemap
La integración @astrojs/sitemap genera un sitemap-index.xml con todas las páginas del sitio durante el build. Es fundamental para que los motores de búsqueda descubran tu contenido.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://docs.example.com', // Obligatorio para sitemap
integrations: [
sitemap({
filter: (page) =>
// Excluir páginas internas del sitemap
!page.includes('/admin/') &&
!page.includes('/preview/'),
changefreq: 'weekly',
priority: 0.7,
lastmod: new Date(),
}),
],
}); Optimización de imágenes con astro:assets
El componente <Image /> de astro:assets optimiza imágenes automáticamente: convierte a formatos modernos (WebP/AVIF), genera tamaños responsive y aplica lazy loading.
---
// src/pages/docs/guia.astro
import { Image } from 'astro:assets';
// Imágenes locales — se optimizan en build-time
import diagramaArq from '../../assets/images/diagrama-arquitectura.png';
import capturaUI from '../../assets/images/captura-dashboard.png';
---
<!-- Imagen local optimizada: genera WebP, calcula width/height automáticamente -->
<Image
src={diagramaArq}
alt="Diagrama de la arquitectura del sistema"
widths={[400, 800, 1200]}
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
format="webp"
quality={80}
/>
<!-- Imagen con densidad de píxeles para pantallas retina -->
<Image
src={capturaUI}
alt="Captura del dashboard principal"
width={800}
densities={[1.5, 2]}
/>
<!-- Imagen remota — requiere configurar dominios permitidos -->
<Image
src="https://cdn.example.com/images/banner.jpg"
alt="Banner promocional"
width={1200}
height={630}
format="avif"
/> // astro.config.mjs — Permitir dominios de imágenes remotas
import { defineConfig } from 'astro/config';
export default defineConfig({
image: {
domains: ['cdn.example.com', 'images.unsplash.com'],
// Servicio de optimización (sharp por defecto)
service: { entrypoint: 'astro/assets/services/sharp' },
},
}); Imágenes remotas
Las imágenes remotas requieren width y height explícitos porque Astro
no puede calcular las dimensiones en build-time. Siempre declara los dominios
permitidos en astro.config.mjs para evitar errores durante el build.
Estrategias de prefetching
Astro puede precargar las páginas a las que el usuario probablemente navegará, haciendo que las transiciones sean prácticamente instantáneas.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
prefetch: {
// Estrategia por defecto para todos los enlaces
defaultStrategy: 'hover',
// Precargar solo páginas del mismo sitio
prefetchAll: false,
},
}); | Estrategia | Comportamiento | Uso recomendado |
|---|---|---|
hover | Precarga cuando el cursor se posiciona sobre el enlace | Navegación principal, enlaces frecuentes. Buen equilibrio entre rendimiento y consumo de datos |
viewport | Precarga cuando el enlace entra en el viewport | Listados de artículos, enlaces visibles. Más agresivo pero mejora la percepción de velocidad |
load | Precarga todos los enlaces al cargar la página | Sitios pequeños con pocas páginas. Consume más ancho de banda |
tap | Precarga cuando el usuario hace click/touch en el enlace | Conexiones lentas o móviles. La mejora es mínima pero ahorra datos |
<!-- Sobreescribir la estrategia por defecto en un enlace específico -->
<a href="/docs/guia-avanzada" data-astro-prefetch="viewport">
Guía avanzada
</a>
<!-- Desactivar prefetch en un enlace externo o poco visitado -->
<a href="/legacy/deprecated" data-astro-prefetch="false">
Documentación antigua
</a> Performance budgets y buenas prácticas
Establecer presupuestos de rendimiento ayuda a mantener la calidad del sitio a medida que crece. Estos son los objetivos recomendados para un sitio de documentación con Astro.
| Métrica Lighthouse | Objetivo | Cómo lograrlo en Astro |
|---|---|---|
| Performance | 95-100 | HTML estático por defecto, zero JS innecesario. Usar client:visible en vez de client:load siempre que sea posible |
| First Contentful Paint | < 1.0s | Evitar fuentes externas bloqueantes. Usar <link rel="preload"> para fuentes críticas |
| Largest Contentful Paint | < 1.5s | Optimizar imágenes hero con <Image />, usar loading="eager" y fetchpriority="high" en la imagen principal |
| Total Blocking Time | < 50ms | Minimizar islas con client:load. Preferir client:idle o client:visible |
| Cumulative Layout Shift | < 0.05 | Siempre declarar width y height en imágenes. Reservar espacio para contenido dinámico |
| Accessibility | 100 | HTML semántico, atributos alt en imágenes, contraste adecuado, navegación por teclado |
| Best Practices | 100 | HTTPS, sin errores en consola, imágenes en formato moderno (WebP/AVIF) |
| SEO | 100 | Componente SEO con meta tags, sitemap.xml, robots.txt, URLs canónicas |
Auditar antes de cada deploy
Ejecuta npx unlighthouse --site https://tu-sitio.com para auditar todas
las páginas del sitio en una sola pasada. Combínalo con
astro build && npx serve dist para auditar localmente antes de subir a
producción.