Sugerencias
← TIL
~3 min de lectura
#astro#typescript#dx#performance

[Astro 6] Guía de migración: Zod 4 y Content Layer

[Astro 6] Guía de migración: Zod 4 y Content Layer#

Astro 6 consolida el Content Layer como el estándar de oro para la gestión de datos, eliminando las colecciones legacy. La migración requiere mover tu configuración a src/content.config.ts, actualizar a Node.js 22 obligatoriamente y ajustar selectores CSS :global que ahora son procesados por un parser más estricto.


Si estás pensando en actualizar tu sitio a Astro 6, no creas que es solo un cambio de versión en el package.json. Esta versión marca el fin de una era para las colecciones clásicas y eleva la vara de la infraestructura necesaria para correr el framework.

Tras migrar campa.dev, una experiencia que inicié en mi post sobre la ingeniería detrás de este portfolio, me encontré con varios muros que no están tan claros en la documentación oficial. Si sos un dev senior buscando una transición limpia, esto es lo que tenés que saber.

1. El requisito de infraestructura: Node.js 22#

Astro 6 ya no soporta Node 20. Si intentás correr astro check o astro build en un entorno con Node 20, tu pipeline de CI/CD va a explotar con un error de soporte.

La solución: Actualizá tu flujo de trabajo de GitHub Actions (o el entorno de tu servidor) inmediatamente.

# .github/workflows/ci.yml
- name: Setup Node
  uses: actions/setup-node@v4
  with:
    node-version: 22 # Requisito mínimo para Astro 6
    cache: "pnpm"

2. Adiós config.ts, hola content.config.ts#

El Content Layer ahora vive en la raíz de src/ con un nuevo nombre. El cambio principal es pasar de leer archivos directamente a usar Loaders.

  • src/
    • content/
      • blog/
      • til/
      • content.config.ts Nuevo archivo único de configuración

Para tus colecciones locales (Markdown/MDX), ahora usás el loader glob:

// src/content.config.ts
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";

const blog = defineCollection({
  // El loader 'glob' permite compilaciones incrementales mucho más rápidas
  loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/blog" }),
  schema: ({ image }) =>
    z.object({
      title: z.string().max(80),
      pubDate: z.date(),
      heroImage: image().optional(),
    }),
});

3. Zod 4 y la estrictez de fechas#

Astro 6 viene con Zod 4, que es notablemente más estricto con los tipos date. Si tus archivos Markdown tienen fechas en formato string que antes pasaban sin problemas, ahora podrías ver errores de validación.

Usa z.coerce.date() para forzar el parseo de strings ISO 8601 de forma segura:

const til = defineCollection({
  loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/til" }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(), // Más resiliente con frontmatter legacy
  }),
});

4. Gotchas de Trinchera (Lo que me pasó a mí)#

El error silencioso de robots.txt#

Si usás integraciones como astro-robots-txt, asegurate de no dejar strings vacíos en tus arrays de configuración. Astro 6 y sus validadores de esquema detectan esto como un error de tipo String must contain at least 1 character(s).

El Parser de CSS y :global#

El nuevo parser de CSS es más estricto con el scoping. Si tenés selectores combinando clases locales con :global, necesitás un espacio entre ellos.

  • Mal (Astro 6 falla): .post-content:global(dl)
  • Bien (Astro 6 feliz): .post-content :global(dl)

Sin ese espacio, el minificador podría generar selectores inválidos como .post-content[data-astro-cid-xxxx]dl, rompiendo tus estilos en producción.


g CO₂
Enlace copiado al portapapeles