Operaciones de Astro
Hechos verificados al 2026-07.
Patrones completos para el desarrollo con el framework Astro: arquitectura de islas, colecciones de contenido, estrategias de renderizado, transiciones de vista y despliegue multiplataforma.
Árbol de Decisión de Estrategia de Renderizado
¿Qué estrategia de renderizado?
│
├─ ¿El contenido es mayormente estático (blog, docs, marketing)?
│ ├─ SÍ → ¿Cambia menos de una vez al día?
│ │ ├─ SÍ → SSG (output: 'static')
│ │ │ TTFB más rápido, almacenable en CDN, costo de ejecución cero
│ │ └─ NO → Híbrido (output: 'hybrid')
│ │ Estático por defecto + SSR opt-in por ruta
│ └─ NO → ¿Necesita personalización cada página?
│ ├─ SÍ → SSR (output: 'server')
│ │ Dinámico por solicitud, consciente de autenticación, datos en tiempo real
│ └─ NO → Híbrido (output: 'hybrid')
│ Caparazón estático + islas de servidor para partes dinámicas
│
├─ ¿La aplicación necesita interactividad en tiempo real (dashboard, SPA)?
│ ├─ SÍ → ¿Es un SPA completo con enrutamiento del lado del cliente?
│ │ ├─ SÍ → Considera React/Vue SPA en su lugar, o Astro + client:only
│ │ └─ NO → Híbrido + arquitectura de islas
│ │ Islas interactivas en páginas estáticas
│ └─ NO → SSG (output: 'static')
│
├─ ¿Preocupa el tiempo de construcción (>10k páginas)?
│ ├─ SÍ → Híbrido con renderizado bajo demanda
│ │ Prerrenderizar páginas populares, SSR para la cola larga
│ └─ NO → SSG lo maneja bien
│
└─ ¿Necesitas computación en el borde (baja latencia global)?
├─ SÍ → SSR + adaptador Cloudflare/Vercel Edge
└─ NO → SSR + adaptador Node o SSG
Configuración
// astro.config.mjs
import { defineConfig } from 'astro/config';
// SSG (por defecto) - todas las páginas prerrenderizadas en tiempo de construcción
export default defineConfig({
output: 'static',
});
// SSR - todas las páginas renderizadas bajo solicitud
export default defineConfig({
output: 'server',
adapter: cloudflare(), // o vercel(), netlify(), node()
});
// Híbrido - estático por defecto, SSR opt-in por página
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// En modo híbrido, opta POR NO prerrenderizar páginas específicas:
export const prerender = false;
// En modo SSR, opta POR prerrenderizar:
export const prerender = true;
---
Referencia Rápida de Arquitectura de Islas
| Directiva | Hidrata Cuando | JS Enviado | Caso de Uso |
|---|---|---|---|
client:load | Inmediatamente al cargar la página | Bundle completo | Interactividad sobre el pliegue (nav, CTA hero) |
client:idle | Cuando la página está inactiva (requestIdleCallback) | Bundle completo | Interactividad bajo el pliegue (formulario de comentarios, chat) |
client:visible | Cuando se desplaza al viewport | Bundle completo | Componentes al final de la página (widget de pie de página, carrusel) |
client:media | Cuando la media query coincide | Bundle completo | Nav solo para móviles, componentes responsive |
client:only="react" | Inmediatamente, omite SSR por completo | Bundle completo | Componentes que no pueden usar SSR (canvas, WebGL) |
| (ninguna) | Nunca - solo HTML estático | Cero JS | Contenido estático, tarjetas, encabezados |
---
import NavBar from '../components/NavBar.tsx';
import CommentForm from '../components/CommentForm.tsx';
import ImageCarousel from '../components/ImageCarousel.svelte';
import MobileMenu from '../components/MobileMenu.vue';
import ThreeScene from '../components/ThreeScene.tsx';
---
<!-- Se carga inmediatamente - interactividad crítica -->
<NavBar client:load />
<!-- Se carga cuando la página está inactiva - no crítico -->
<CommentForm client:idle />
<!-- Se carga al desplazarse al viewport - perezoso -->
<ImageCarousel client:visible />
<!-- Se carga solo en móvil -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Solo cliente, sin SSR (WebGL no puede ejecutarse en el servidor) -->
<ThreeScene client:only="react" />
Inicio Rápido de Colecciones de Contenido
Definir Esquema
// src/content.config.ts (Astro 5) o src/content/config.ts (Astro 4)
import { defineCollection, z, reference } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: reference('authors'), // Referencia a otra colección
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
avatar: z.string(),
bio: z.string(),
socials: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}).optional(),
}),
});
export const collections = { blog, authors };
Consultar Colecciones
---
import { getCollection, getEntry } from 'astro:content';
// Obtener todas las publicaciones de blog no borrador, ordenadas por fecha
const posts = (await getCollection('blog', ({ data }) => !data.draft))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// Obtener una sola entrada
const post = await getEntry('blog', 'my-first-post');
// Resolver una referencia
const author = await getEntry(post.data.author);
// Renderizar contenido
const { Content, headings } = await post.render();
---
<Content />
Colecciones de Contenido vs CMS Externo
| Criterio | Colecciones de Contenido | CMS Externo (Payload, etc.) |
|---|---|---|
| Tipo de contenido | Markdown/MDX local, docs, blogs | Modelos de datos relacionales |
| Autores | Desarrolladores (control de versiones) | Editores (UI de administración, autenticación multiusuario) |
| Validación | Tipos seguros vía Zod en tiempo de construcción | Esquemas del CMS + contratos API |
| Cadencia de actualización | Se despliega con el sitio | Independiente de los despliegues |
| Necesidades de API | Ninguna (consultas en tiempo de construcción) | REST/GraphQL para otros consumidores |
| Flujo de trabajo | PRs de Git, revisión simple | Flujos editoriales, borradores, roles |
Regla general: comienza con Colecciones de Contenido; recurre a un CMS solo cuando desarrolladores no técnicos necesiten publicar sin un despliegue, o cuando el contenido sea genuinamente relacional.
Referencia de Estructura del Proyecto
project-root/
├── astro.config.mjs # Configuración de Astro
├── tsconfig.json # Configuración de TypeScript (extiende astro/tsconfigs)
├── package.json
├── public/ # Activos estáticos (copiados tal cual)
│ ├── favicon.svg
│ ├── robots.txt
│ └── og-image.png
├── src/
│ ├── pages/ # Enrutamiento basado en archivos
│ │ ├── index.astro # → /
│ │ ├── about.astro # → /about
│ │ ├── blog/
│ │ │ ├── index.astro # → /blog
│ │ │ └── [slug].astro # → /blog/:slug (dinámico)
│ │ ├── api/
│ │ │ └── search.ts # → /api/search (endpoint API)
│ │ └── [...slug].astro # → catch-all/404
│ ├── layouts/
│ │ ├── BaseLayout.astro # Caparazón HTML, <head>, estilos globales
│ │ └── BlogPost.astro # Diseño de publicación de blog
│ ├── components/
│ │ ├── Header.astro # Componente Astro estático
│ │ ├── Footer.astro
│ │ ├── NavBar.tsx # Isla React
│ │ └── Counter.svelte # Isla Svelte
│ ├── content/ # Archivos fuente de colecciones de contenido
│ │ ├── blog/
│ │ │ ├── post-one.md
│ │ │ └── post-two.mdx
│ │ └── authors/
│ │ └── jane.json
│ ├── content.config.ts # Esquemas de colecciones (Astro 5)
│ ├── middleware.ts # Middleware de solicitud/respuesta
│ ├── styles/
│ │ └── global.css
│ └── lib/ # Utilidades compartidas
│ ├── utils.ts
│ └── constants.ts
└── .env # Variables de entorno
Referencia Rápida de Transiciones de Vista
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
Directivas de Transición
<!-- Persistir elemento entre páginas (mantiene estado, evita re-renderizado) -->
<audio transition:persist id="player">
<source src="/music.mp3" />
</audio>
<!-- Transición nombrada para emparejamiento de animación -->
<img transition:name="hero" src={post.heroImage} />
<!-- Animación personalizada -->
<div transition:animate="slide">Contenido</div>
<div transition:animate="fade">Contenido</div>
<div transition:animate="none">Sin animación</div>
<!-- Persistir con nombre (para múltiples elementos persistentes) -->
<video transition:persist="media-player" />
Eventos del Ciclo de Vida
<script>
document.addEventListener('astro:before-preparation', (e) => {
// Antes de obtener la nueva página - cancelar navegación, mostrar carga
});
document.addEventListener('astro:after-preparation', (e) => {
// Nueva página obtenida, antes del intercambio
});
document.addEventListener('astro:before-swap', (e) => {
// Personalizar el comportamiento del intercambio DOM
});
document.addEventListener('astro:after-swap', () => {
// DOM actualizado - reinicializar scripts
});
document.addEventListener('astro:page-load', () => {
// Página completamente cargada (se dispara en la carga inicial y en cada navegación)
// Úsalo en lugar de DOMContentLoaded con View Transitions
});
</script>
Manejo de Atrás/Adelante
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true, // Precargar todos los enlaces al pasar el ratón
defaultStrategy: 'hover', // 'hover' | 'tap' | 'viewport' | 'load'
},
});
<!-- Control de precarga por enlace -->
<a href="/about" data-astro-prefetch>Precargar al pasar el ratón (por defecto)</a>
<a href="/blog" data-astro-prefetch="viewport">Precargar cuando sea visible</a>
<a href="/contact" data-astro-prefetch="load">Precargar inmediatamente</a>
<a href="/external" data-astro-prefetch="false">Sin precarga</a>
Árbol de Decisión de Despliegue
¿Dónde desplegar?
│
├─ ¿Necesitas computación en el borde + ecosistema Cloudflare (KV, D1, R2)?
│ └─ Cloudflare Pages/Workers
│ Adaptador: @astrojs/cloudflare
│ Mejor para: Borde global, enlaces Workers, rentabilidad
│
├─ ¿Necesitas serverless + ecosistema Vercel (ISR, analíticas)?
│ └─ Vercel
│ Adaptador: @astrojs/vercel
│ Mejor para: Migración desde Next.js, optimización de imágenes, ISR
│
├─ ¿Necesitas serverless + ecosistema Netlify (formularios, identidad)?
│ └─ Netlify
│ Adaptador: @astrojs/netlify
│ Mejor para: JAMstack, formularios integrados, pruebas divididas
│
├─ ¿Necesitas control total del servidor (Docker, runtime personalizado)?
│ └─ Node.js (independiente o Express/Fastify)
│ Adaptador: @astrojs/node
│ Mejor para: Autogestionado, WebSocket, procesos de larga duración
│
└─ ¿Sitio puramente estático (sin necesidad de SSR)?
└─ Cualquier host estático (GitHub Pages, S3, Cloudflare Pages)
Sin adaptador necesario, output: 'static'
Mejor para: Blogs, docs, sitios de marketing
Instalación de Adaptador
# Cloudflare
npx astro add cloudflare
# Vercel
npx astro add vercel
# Netlify
npx astro add netlify
# Node.js
npx astro add node
Errores Comunes
| Error | Por qué | Solución |
|---|---|---|
| Errores de discrepancia de hidratación | El HTML del servidor difiere del renderizado del cliente (fechas, IDs aleatorios, APIs del navegador) | Usa client:only para componentes dependientes del navegador, o asegura un renderizado determinista |
import.meta.env indefinido en el cliente | Solo las variables con prefijo PUBLIC_ están expuestas al código del lado del cliente | Renombra a PUBLIC_MY_VAR o pásalas vía props desde el servidor |
| Rutas dinámicas 404 en SSG | getStaticPaths() no devuelve todos los parámetros posibles | Asegúrate de que getStaticPaths() devuelva cada ruta válida, o cambia a híbrido/SSR |
| Imágenes no optimizadas | Usar <img> en lugar del componente <Image /> de Astro | Importa desde astro:assets: import { Image } from 'astro:assets' y usa importaciones locales para src |
| SSR falla sin adaptador | output: 'server' o 'hybrid' requiere un adaptador de despliegue | Instala el adaptador: npx astro add cloudflare (o vercel, netlify, node) |
| Componentes MDX no se renderizan | Los componentes personalizados no se pasan al contenido MDX | Pasa componentes vía <Content components={{ MyComponent }} /> o usa la configuración MDX en astro.config.mjs |
| Los cambios en el esquema de la colección de contenido no se reflejan | La generación de tipos está en caché, tipos .astro obsoletos | Ejecuta astro sync para regenerar los tipos, reinicia el servidor de desarrollo |
client:* en componentes Astro | Las directivas de cliente solo funcionan en componentes de framework (React, Vue, Svelte) | Los componentes Astro son solo estáticos; extrae las partes interactivas a un componente del framework |
document / window no está definido | El código del lado del servidor no puede acceder a los globales del navegador | Protege con if (typeof window !== 'undefined') o muévelo a client:only |
| Los estilos se filtran entre componentes | Usar CSS global en lugar de estilos con ámbito | Usa <style> (con ámbito por defecto en .astro) o <style is:global> intencionalmente |
| Los scripts se rompen con View Transitions | DOMContentLoaded solo se dispara una vez con View Transitions | Usa el evento astro:page-load en su lugar, que se dispara en cada navegación |
| Variables de entorno faltan en producción | .env no cargado o variables de entorno de la plataforma no configuradas | Usa envField en astro.config.mjs para validación; configura las variables en el panel de la plataforma |
Lista de Verificación de Seguridad en Producción
Para cada despliegue de producción, aborda:
- Encabezados CSP - configura un
Content-Security-Policyrestrictivo (ver patrones de middleware enreferences/deployment.md) - Restricciones de imágenes remotas - aplica listas de permitidos explícitas
image.domains/remotePatterns; nunca derives URLs de imágenes de la entrada del usuario (riesgo SSRF) - Validación del encabezado Host - verifica que el host de la solicitud coincida con los dominios esperados en el middleware (solo SSR/híbrido)
- Gestión de secretos - en Cloudflare, usa Workers Bindings (
wrangler secret put), no variables de entorno fijadas en el código; en otros lugares, usa almacenes de secretos de la plataforma - Solo HTTPS - asegúrate de que todos los recursos externos (scripts, imágenes, fuentes) se carguen a través de HTTPS
- Validación de entrada - sanitiza toda la entrada del usuario en contextos SSR (parámetros de consulta, cuerpos de formularios, cookies)
Archivos de Referencia
| Archivo | Contenidos | Líneas |
|---|---|---|
references/content-collections.md | Patrones de esquema, tipos Zod, consultas, MDX, API de capa de contenido, migraciones | ~500 |
references/islands-rendering ... [truncado para traducción] |