La primera vez que vi los resultados de Lighthouse de un sitio en Astro me quedé sin palabras. Cien sobre cien en performance, en accesibilidad y en SEO. No después de horas de optimización manual, sino por defecto, desde el primer despliegue. Eso no ocurre con Next.js ni con cualquier otro framework sin un trabajo serio de configuración.
Astro es un framework web diseñado específicamente para sitios de contenido: blogs, documentación, portafolios, landing pages, sitios de marketing. Su apuesta es enviar cero JavaScript al navegador por defecto y renderizar todo en el servidor. Si un componente necesita interactividad, lo añades explícitamente. El resto es HTML estático.
La versión 5, lanzada a finales de 2024, añadió Server Islands, una mejora sustancial del Content Layer y mejor soporte para SSR parcial. Esta guía cubre todo lo que necesitas para crear un blog o sitio de contenido real con Astro 5 en 2026.
Creando el proyecto: estructura y configuración básica
Astro tiene un CLI que guía la configuración inicial:
npm create astro@latest mi-blog
# El CLI pregunta:
# - Plantilla (Blog, Portfolio, Empty...)
# - TypeScript (sí, recomendado)
# - Instalar dependencias
cd mi-blog
npm run dev
La estructura de carpetas de un proyecto Astro:
mi-blog/
├── src/
│ ├── components/ # Componentes .astro
│ ├── layouts/ # Layouts reutilizables
│ ├── pages/ # Rutas (file-based routing)
│ ├── content/ # Colecciones de contenido (Markdown/MDX)
│ └── styles/ # CSS global
├── public/ # Assets estáticos (imágenes, fuentes...)
├── astro.config.mjs # Configuración de Astro
└── tsconfig.json
El routing en Astro es file-based: un archivo en src/pages/about.astro genera la URL /about. Un archivo src/pages/blog/[slug].astro genera rutas dinámicas. Familiar si has usado Next.js.
Los componentes .astro: sintaxis y funcionamiento
Los componentes Astro tienen una sintaxis propia que separa el código del servidor (frontmatter) del template HTML:
---
// src/components/TarjetaArticulo.astro
// Todo lo que está aquí se ejecuta en el servidor
import type { CollectionEntry } from 'astro:content';
interface Props {
articulo: CollectionEntry<'blog'>;
}
const { articulo } = Astro.props;
const { titulo, fecha, descripcion, imagen } = articulo.data;
const fechaFormateada = new Date(fecha).toLocaleDateString('es-ES', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<!-- Template HTML -->
<article class="tarjeta">
{imagen && (
<img src={imagen} alt={titulo} loading="lazy" />
)}
<div class="contenido">
<time datetime={fecha.toISOString()}>{fechaFormateada}</time>
<h2><a href={`/blog/${articulo.slug}`}>{titulo}</a></h2>
<p>{descripcion}</p>
</div>
</article>
<style>
/* CSS con scope automático al componente */
.tarjeta { border-radius: 8px; overflow: hidden; }
.tarjeta img { width: 100%; aspect-ratio: 16/9; object-fit: cover; }
.contenido { padding: 1.5rem; }
time { font-size: 0.875rem; color: var(--color-texto-suave); }
</style>
El CSS dentro del bloque <style> tiene scope automático al componente. Astro genera clases únicas para evitar colisiones, sin necesidad de CSS Modules ni Styled Components.
Content Collections: gestionar contenido con tipado
Una de las funcionalidades más potentes de Astro es el sistema de Content Collections. Permite definir esquemas tipados para tus archivos Markdown o MDX:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content', // Markdown/MDX
schema: z.object({
titulo: z.string(),
descripcion: z.string().max(160),
fecha: z.date(),
imagen: z.string().optional(),
categoria: z.enum(['JavaScript', 'TypeScript', 'CSS', 'Backend']),
borrador: z.boolean().default(false),
etiquetas: z.array(z.string()).default([]),
}),
});
export const collections = { blog };
Luego creas tus artículos en src/content/blog/ como archivos Markdown:
---
# src/content/blog/guia-prisma-orm.md
titulo: "Prisma ORM con TypeScript: de cero a producción"
descripcion: "Aprende Prisma ORM con TypeScript desde cero..."
fecha: 2026-03-15
categoria: "Backend"
etiquetas: ["TypeScript", "Node.js", "PostgreSQL"]
---
## Introducción
Antes de Prisma, trabajar con bases de datos en Node.js...
Y accedes a ellos con las funciones del Content API, completamente tipadas:
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import TarjetaArticulo from '../../components/TarjetaArticulo.astro';
// getCollection devuelve un array tipado con CollectionEntry<'blog'>
const articulos = await getCollection('blog', ({ data }) => {
// Filtrar borradores en producción
return import.meta.env.PROD ? !data.borrador : true;
});
// Ordenar por fecha descendente
articulos.sort((a, b) => b.data.fecha.valueOf() - a.data.fecha.valueOf());
---
<Layout titulo="Blog">
<h1>Artículos</h1>
<div class="grid">
{articulos.map(articulo => (
<TarjetaArticulo articulo={articulo} />
))}
</div>
</Layout>
Rutas dinámicas y generación estática
Para generar páginas individuales para cada artículo, usas un archivo de ruta dinámica que exporta getStaticPaths:
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
export async function getStaticPaths() {
const articulos = await getCollection('blog');
return articulos.map(articulo => ({
params: { slug: articulo.slug },
props: { articulo },
}));
}
const { articulo } = Astro.props;
const { Content } = await articulo.render();
---
<Layout titulo={articulo.data.titulo} descripcion={articulo.data.descripcion}>
<article>
<h1>{articulo.data.titulo}</h1>
<Content />
</article>
</Layout>
Astro genera un archivo HTML estático por cada artículo durante el build. Sin servidor, sin JavaScript de runtime. El resultado es un sitio que puede servirse desde cualquier CDN con latencia mínima.
Islands Architecture: añadir interactividad donde la necesitas
Astro introduce el concepto de Islands: componentes interactivos aislados dentro de páginas estáticas. Puedes usar React, Vue, Svelte o Solid para estos componentes, mientras el resto de la página es HTML estático.
---
// Instalar la integración de React
// npx astro add react
import BuscadorInteractivo from '../components/BuscadorInteractivo.jsx';
import ContadorVistas from '../components/ContadorVistas.jsx';
---
<!-- Este componente carga JavaScript solo cuando es visible -->
<BuscadorInteractivo client:visible />
<!-- Este carga JavaScript inmediatamente -->
<ContadorVistas client:load />
<!-- Este espera a que el navegador esté idle -->
<ComentariosDesqus client:idle />
<!-- Este solo hidrata en media queries específicos -->
<MenuMobile client:media="(max-width: 768px)" />
Las directivas client:* controlan exactamente cuándo se carga y ejecuta el JavaScript del componente. client:visible es la más usada para componentes que aparecen al hacer scroll: el JS solo se descarga cuando el componente entra en el viewport.
Server Islands en Astro 5: contenido dinámico en páginas estáticas
Astro 5 introdujo Server Islands, una funcionalidad que permite incluir partes dinámicas (personalizadas por usuario) dentro de páginas estáticas, sin perder la velocidad del SSG:
---
// src/components/RecomendacionesPersonalizadas.astro
// Este componente se renderiza en el servidor en cada petición
// mientras el resto de la página viene del CDN (estática)
const usuario = await obtenerUsuarioActual(Astro.cookies);
const recomendaciones = await obtenerRecomendaciones(usuario?.id);
---
<div>
{recomendaciones.map(art => (
<a href={`/blog/${art.slug}`}>{art.titulo}</a>
))}
</div>
En la página que lo usa:
---
import RecomendacionesPersonalizadas from '../components/RecomendacionesPersonalizadas.astro';
---
<!-- El resto de la página es estática (sirve del CDN) -->
<h1>Blog de TuRinconDev</h1>
<ListaArticulosEstatica />
<!-- Esta isla se renderiza en el servidor dinámicamente -->
<RecomendacionesPersonalizadas server:defer />
SEO automático con el componente Head
Para un blog, el SEO es fundamental. Astro hace fácil crear un layout con todas las etiquetas meta necesarias:
---
// src/layouts/Layout.astro
interface Props {
titulo: string;
descripcion?: string;
imagen?: string;
tipo?: 'article' | 'website';
}
const {
titulo,
descripcion = 'Blog de desarrollo web en español',
imagen = '/og-default.jpg',
tipo = 'website',
} = Astro.props;
const urlActual = Astro.url.toString();
---
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{titulo} | TuRinconDev</title>
<meta name="description" content={descripcion} />
<!-- Open Graph -->
<meta property="og:title" content={titulo} />
<meta property="og:description" content={descripcion} />
<meta property="og:image" content={imagen} />
<meta property="og:url" content={urlActual} />
<meta property="og:type" content={tipo} />
<!-- Canonical -->
<link rel="canonical" href={urlActual} />
<!-- RSS -->
<link rel="alternate" type="application/rss+xml" title="TuRinconDev" href="/rss.xml" />
</head>
<body>
<slot />
</body>
</html>
Feed RSS automático
Astro tiene una integración oficial para generar el feed RSS automáticamente a partir de tus colecciones de contenido:
// Instalar la integración
// npm install @astrojs/rss
// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const articulos = await getCollection('blog', ({ data }) => !data.borrador);
articulos.sort((a, b) => b.data.fecha - a.data.fecha);
return rss({
title: 'TuRinconDev - Blog de desarrollo web',
description: 'Tutoriales y guías de desarrollo web en español',
site: context.site,
items: articulos.map(articulo => ({
title: articulo.data.titulo,
pubDate: articulo.data.fecha,
description: articulo.data.descripcion,
link: `/blog/${articulo.slug}/`,
})),
});
}
Despliegue: Vercel, Netlify o estático puro
El modo por defecto de Astro es SSG (generación estática). Para desplegar en Vercel o Netlify basta con conectar el repositorio y configurar el comando de build:
# Comando de build para producción
npm run build
# Genera la carpeta dist/ con HTML estático listo para desplegar
# Para Vercel/Netlify: añadir el adaptador
npx astro add vercel
# O
npx astro add netlify
Si usas Server Islands o SSR parcial, necesitas el adaptador del proveedor para que las partes dinámicas se ejecuten en Edge Functions. Para un blog puro sin personalización, el modo SSG sin adaptador funciona perfectamente y produce el sitio más rápido posible.
Siguiente paso
El mejor primer proyecto con Astro es exactamente lo que describes: un blog. Crea el proyecto con la plantilla de blog incluida, escribe tres o cuatro artículos reales en Markdown, añade el feed RSS y despliégalo en Vercel. Todo ese proceso debería llevar menos de dos horas.
Desde ahí, el siguiente nivel es añadir búsqueda. Pagefind es la solución más popular para búsqueda estática en Astro: indexa tu contenido durante el build y funciona sin servidor. La integración oficial de Astro hace que configurarlo sea cuestión de minutos. Con eso tendrás un blog completamente funcional, rápido y sin costes de servidor.