---
name: astro-static-site
description: Construye y mantiene sitios estáticos en Astro 4/5 con content collections, imágenes optimizadas e islas mínimas. Úsala al crear páginas, componentes .astro, colecciones de contenido o al revisar el rendimiento de un sitio Astro.
version: 1.0.0
license: CC-BY-4.0
updated: 2026-08-16
---

# Astro: sitio estático rápido

Guía de trabajo para sitios Astro con salida estática. El objetivo por defecto
es **cero JavaScript en cliente** salvo donde la interacción lo exija.

## Reglas de oro

1. **HTML primero.** Un componente `.astro` se renderiza en build. No añadas
   `client:*` hasta que exista una interacción real que lo necesite.
2. **`client:visible` antes que `client:load`.** Si la isla está bajo el
   pliegue, no debe competir con el primer render.
3. **Imágenes siempre por `astro:assets`.** `<Image />` o `<Picture />` con
   `widths` y `sizes`. Una imagen en `public/` no se optimiza: solo va ahí lo
   que necesita una URL estable (favicon, PDF, `robots.txt`).
4. **Fuentes self-hosted** vía `@fontsource*`, nunca CDN de Google: elimina una
   conexión de terceros y el FOUT asociado.

## Content collections

Define el esquema en `src/content/config.ts` y deja que Zod valide en build:

```ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: ({ image }) => z.object({
    title:       z.string(),
    description: z.string(),
    pubDate:     z.coerce.date(),
    tags:        z.array(z.string()).default([]),
    draft:       z.boolean().default(false),
    heroImage:   image().optional(),
  }),
});

export const collections = { blog };
```

- `image()` en el esquema es lo que permite optimizar la portada. Requiere que
  el fichero viva dentro de `src/`, no en `public/`.
- Filtra los borradores en una única función (`getPublishedPosts()`), no en
  cada página: así no se escapa ninguno.

## Rutas dinámicas

```astro
---
export async function getStaticPaths() {
  const posts = await getCollection('blog', ({ data }) => !data.draft);
  return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Content />
```

## Revisión de rendimiento

- `npm run build` y comprueba que el bundle de `_astro/*.js` sea el esperado:
  si aparece un framework entero, hay una isla de más.
- Toda imagen debe llegar al HTML con `width` y `height` para no provocar CLS.
- El script anti-FOUC del tema va **inline en el `<head>`**, antes del CSS.

## Errores frecuentes

| Síntoma | Causa habitual |
|---|---|
| La imagen no se optimiza | Está en `public/` en vez de `src/` |
| CLS alto al cargar | Falta `width`/`height` o `aspect-ratio` |
| Parpadeo de tema | El script del tema no es inline y bloqueante |
| Tags con URLs rotas | Falta slugificar acentos y símbolos (`.NET`, `C#`) |
