Astro ya era mi SSG favorito. Con la versión 5, da un salto que resuelve dos de los problemas más gordos que tenía: cómo meter contenido dinámico en un sitio estático sin romper el rendimiento, y cómo gestionar datos que no vienen de archivos Markdown.
Server Islands
El problema clásico de los SSG: tienes un sitio 100% estático pero necesitas un componente dinámico — un carrito, un contador, un widget de sesión. Hasta ahora tocaba cargar toda la página con JS o usar iframes.
Server Islands usa la directiva server:defer para marcar componentes
que se cargan de forma diferida desde el servidor:
---
import { Cart } from '../components/Cart.astro';
import ProductGrid from '../components/ProductGrid.astro';
---
<!-- Esto se renderiza estáticamente en build time -->
<ProductGrid products={products} />
<!-- Esto se carga dinámicamente después, desde el servidor -->
<Cart server:defer />
El HTML estático se envía inmediatamente. El componente con
server:defer se fetcha después y se inyecta en la página sin JS en
el cliente. El navegador recibe HTML puro en ambos casos.
Puedes pasar props al componente diferido:
<Cart server:defer userId={user.id} />
Y combinarlo con directivas de cliente para componentes interactivos: un componente puede ser estático, diferido o hidratado en el cliente, según lo que necesites.
Content Layer
La otra gran novedad. Hasta Astro 4, las content collections solo cargaban archivos locales (Markdown, MDX). Astro 5 introduce el Content Layer: un sistema para cargar datos de cualquier fuente.
La configuración ahora va en src/content.config.ts:
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()),
excerpt: z.string(),
}),
});
export const collections = { blog };
La diferencia clave: z se importa de 'astro:content' (en Astro 5
también puedes importarlo de 'astro/zod'), y el loader remplaza
el antiguo sistema que solo leía archivos.
Puedes crear loaders custom para APIs, bases de datos o lo que sea:
import { defineCollection, z } from 'astro:content';
const proyectos = defineCollection({
loader: async () => {
const res = await fetch('https://api.example.com/projects');
const data = await res.json();
return data.map((item: any) => ({ ...item, id: String(item.id) }));
},
schema: z.object({
id: z.string(),
name: z.string(),
url: z.string().url(),
}),
});
export const collections = { blog, proyectos };
Esto abre la puerta a usar Astro como CMS headless de sí mismo: tus datos viven donde quieras y Astro los consume igual.
Mi perspectiva
Server Islands elimina la excusa de “necesito un framework full SSR para este componente”. Content Layer elimina la excusa de “mis datos no viven en Markdown, no puedo usar un SSG”.
Astro 5 es la primera versión que me hace sentir que no hay compromiso entre estático y dinámico. Y ese es exactamente el punto.