Saltar al contenido
Volver al blog

Migrando al Content Layer API de Astro 5: Lo que Aprendí

6 min de lectura
Astro TypeScript Content Layer Migración Tutorial

Astro 5 trajo un cambio importante: el Content Layer API reemplaza la vieja forma de manejar Content Collections. Acabo de migrar este mismo portafolio y quiero compartir lo que aprendí — incluyendo los errores que me encontré.


🤔 ¿Por qué migrar?

El viejo sistema (type: 'content') sigue funcionando, pero está en modo legacy. El nuevo Content Layer API ofrece:

  • Más flexibilidad — puedes cargar contenido desde cualquier fuente, no solo archivos locales
  • Mejor rendimiento — build incremental más eficiente
  • API unificadaglob(), file(), o loaders personalizados
  • Futuro-proof — las nuevas funciones de Astro se construyen sobre esta API

Si tu proyecto usa Astro 5 y todavía tienes type: 'content' en tu config, es momento de migrar.


📋 Paso 1: Mover el archivo de configuración

El primer cambio es la ubicación del archivo. Ya no vive dentro de src/content/:

❌ src/content/config.ts     (legacy)
✅ src/content.config.ts     (Content Layer API)

Sí, sale de la carpeta content/ y se coloca directamente en src/.


📋 Paso 2: Usar el glob() loader

El cambio más visible es reemplazar type: 'content' con un loader:

 import { defineCollection, z } from 'astro:content';
+import { glob } from 'astro/loaders';

 const blogCollection = defineCollection({
-  type: 'content',
+  loader: glob({
+    pattern: '**/[^_]*.{md,mdx}',
+    base: './src/content/blog',
+  }),
   schema: z.object({
     title: z.string(),
     description: z.string(),
     date: z.coerce.date(),
   }),
 });

El pattern define qué archivos cargar (archivos .md y .mdx que no empiecen con _). El base es la carpeta donde están tus archivos.

Tip: Tus archivos Markdown no necesitan moverse. Solo cambia cómo Astro los encuentra.


⚠️ Paso 3: Arreglar los IDs (el error que nadie te avisa)

Este fue mi primer problema real. Después de migrar, mis URLs se veían así:

❌ /proyectos/mi-sitio-web.md
✅ /proyectos/mi-sitio-web

¿Por qué? El glob() loader genera IDs que incluyen la extensión del archivo. En la API legacy, el ID era solo el nombre sin extensión.

La solución es usar generateId para limpiar los IDs:

const stripExtension = ({ entry }: { entry: string }) =>
  entry.replace(/\.mdx?$/, '');

const blogCollection = defineCollection({
  loader: glob({
    pattern: '**/[^_]*.{md,mdx}',
    base: './src/content/blog',
    generateId: stripExtension, // ← esto arregla las URLs
  }),
  schema: z.object({ ... }),
});

Sin esta línea, tus rutas dinámicas van a generar URLs con .md al final. No rompe el build, pero sí rompe los enlaces internos del sitio.


📋 Paso 4: Cambiar slug por id

En la API legacy, las entries tenían una propiedad slug. En el Content Layer API, eso ya no existe. Se usa id:

 export async function getStaticPaths() {
   const posts = await getCollection('blog');
   return posts.map((post) => ({
-    params: { slug: post.slug },
+    params: { slug: post.id },
     props: { post },
   }));
 }

Esto aplica en todos los lugares donde uses .slug — páginas de listado, detail pages, links internos, etc.


📋 Paso 5: Nuevo render()

Las entries ya no tienen un método .render() propio. Ahora se importa la función render() desde astro:content:

-import { getCollection } from 'astro:content';
+import { getCollection, render } from 'astro:content';

 const post = Astro.props.post;
-const { Content } = await post.render();
+const { Content } = await render(post);

Es un cambio pequeño pero necesario en todas las páginas que renderizan contenido Markdown.


🧹 Paso 6: Limpiar el caché

Después de todos los cambios, limpia la carpeta .astro/ para que Astro regenere el content store:

rm -rf .astro
pnpm build

Si no limpias el caché, puedes ver errores confusos donde los IDs viejos colisionan con los nuevos.


✅ Checklist de migración

#TareaArchivo(s)
1Mover configsrc/content/config.tssrc/content.config.ts
2Agregar glob() loadersrc/content.config.ts
3Agregar generateIdsrc/content.config.ts
4Cambiar slugidTodas las páginas con getStaticPaths
5Importar render()Páginas de detalle (blog, proyectos)
6Limpiar .astro/Terminal
7Verificarpnpm run check && pnpm build

💡 Lo que me sorprendió

  1. El build pasa aunque las URLs estén mal. El .md en las URLs no genera error de build — solo produce rutas que nadie va a visitar. Es silencioso.

  2. La API de schemas no cambia. El schema: ({ image }) => z.object({ ... }) funciona exactamente igual. No necesitas tocar tus schemas.

  3. getCollection() funciona igual. La forma de consultar colecciones no cambia. Solo cambian las propiedades de los objetos que devuelve.

  4. Los archivos de contenido no se mueven. Tus .md se quedan donde están. Solo cambia la config que le dice a Astro dónde encontrarlos.


🔗 Referencias


¿Tienes problemas con la migración? Escríbeme y te ayudo.