Docs / Frontend / Astro Framework

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

text
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

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.

text
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

DirectivaComportamientoCaso de uso
client:loadSe hidrata inmediatamente al cargar la páginaComponentes críticos visibles al inicio
client:idleSe hidrata cuando el navegador está en idleComponentes no urgentes
client:visibleSe hidrata al entrar en el viewportComponentes bajo el fold
client:mediaSe hidrata al cumplir un media queryComponentes solo desktop
client:onlyNo SSR, solo client-side renderComponentes que usan APIs del browser
astro
---
// 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.

astro
---
// 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.

typescript
// 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 };
astro
---
// 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.

javascript
// 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ónComandoFunción
Reactnpx astro add reactComponentes .tsx interactivos
Vuenpx astro add vueComponentes .vue
Tailwindnpx astro add tailwindUtility-first CSS
MDXnpx astro add mdxMarkdown + JSX
Sitemapnpx astro add sitemapSEO sitemap.xml
Nodenpx astro add nodeSSR 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.

astro
---
// 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.

astro
---
// 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>
astro
---
// 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.

DirectivaEfectoUso recomendado
transition:animate="fade"Fundido cruzado (opacidad)Contenido general, imágenes de fondo
transition:animate="slide"Deslizamiento lateral con fadePaneles 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 elementoElementos que deben permanecer estáticos
astro
<!-- 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.

astro
---
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.

EventoMomentoCaso de uso
astro:before-preparationAntes de cargar el contenido de la nueva páginaMostrar indicador de carga, cancelar navegación
astro:after-preparationDespués de cargar el contenido pero antes de intercambiarloModificar el DOM de la nueva página antes de mostrarla
astro:before-swapJusto antes de reemplazar el DOM antiguo con el nuevoAnimaciones de salida personalizadas, guardar estado del scroll
astro:after-swapDespués de reemplazar el DOM, antes de que la transición finaliceReinicializar librerías de terceros, restaurar estado
astro:page-loadLa página nueva está completamente cargada y visibleAnalytics de página vista, inicializar widgets
astro
<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

AspectoSin View TransitionsCon View Transitions
Navegación entre páginasRecarga completa del navegador (flash blanco)Transición fluida animada sin recarga visible
Estado de islasSe destruyen y recrean en cada navegaciónPersistentes con transition:persist
Animaciones entre páginasNo disponibles de forma nativaFade, slide y morph de elementos con nombre
JavaScript adicionalNingunoRouter ligero de Astro (~3 KB)
Compatibilidad navegadoresUniversalFallback automático en navegadores sin soporte
SEOSin cambiosSin impacto — sigue siendo HTML estático
Experiencia percibidaSitio web estático tradicionalSensació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.

astro
---
// 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} />
astro
---
// 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.

text
# 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.

javascript
// 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.

astro
---
// 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"
/>
javascript
// 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.

javascript
// 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,
  },
});
EstrategiaComportamientoUso recomendado
hoverPrecarga cuando el cursor se posiciona sobre el enlaceNavegación principal, enlaces frecuentes. Buen equilibrio entre rendimiento y consumo de datos
viewportPrecarga cuando el enlace entra en el viewportListados de artículos, enlaces visibles. Más agresivo pero mejora la percepción de velocidad
loadPrecarga todos los enlaces al cargar la páginaSitios pequeños con pocas páginas. Consume más ancho de banda
tapPrecarga cuando el usuario hace click/touch en el enlaceConexiones lentas o móviles. La mejora es mínima pero ahorra datos
astro
<!-- 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 LighthouseObjetivoCómo lograrlo en Astro
Performance95-100HTML estático por defecto, zero JS innecesario. Usar client:visible en vez de client:load siempre que sea posible
First Contentful Paint< 1.0sEvitar fuentes externas bloqueantes. Usar <link rel="preload"> para fuentes críticas
Largest Contentful Paint< 1.5sOptimizar imágenes hero con <Image />, usar loading="eager" y fetchpriority="high" en la imagen principal
Total Blocking Time< 50msMinimizar islas con client:load. Preferir client:idle o client:visible
Cumulative Layout Shift< 0.05Siempre declarar width y height en imágenes. Reservar espacio para contenido dinámico
Accessibility100HTML semántico, atributos alt en imágenes, contraste adecuado, navegación por teclado
Best Practices100HTTPS, sin errores en consola, imágenes en formato moderno (WebP/AVIF)
SEO100Componente 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.

END OF DOCUMENT

¿Necesitas más? Volver a la Librería →