karawaci.kode

2026-04-22 · 6 min

Migrate dari Astro 4 content collections ke Astro 5+ Content Layer API

Astro 5 (released late 2024) menggeser content collections dari file-system magic ke explicit loader API. Sebagian besar project Astro 4 break ketika upgrade. Saya migrate 3 project Astro saya dari 4.x ke 5.x bulan lalu, dan dokumentasikan di sini.

TL;DR perbedaan

Astro 4:

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

const posts = defineCollection({
  schema: z.object({ title: z.string() }),
});

export const collections = { posts };

Astro 4 mengasumsikan content ada di src/content/posts/ secara implicit.

Astro 5:

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

const posts = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
  schema: z.object({ title: z.string() }),
});

export const collections = { posts };

Explicit loader, dengan base path yang explicit.

Migration steps

1. Rename src/content/config.ts ke src/content.config.ts

File config sekarang di root src/, bukan di dalam content/ folder. Lebih konsisten dengan Astro convention lain.

mv src/content/config.ts src/content.config.ts

2. Import glob loader

// tambah ini di atas content.config.ts
import { glob } from 'astro/loaders';

3. Convert setiap collection

Untuk setiap defineCollection({ schema }) di file lama, ubah jadi:

defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/<folder>' }),
  schema: <existing schema>,
});

4. Update usage di pages

Sebagian besar API yang sama:

---
// works in both Astro 4 and 5
import { getCollection, getEntry } from 'astro:content';
const posts = await getCollection('posts');
---

Tetapi render() API berubah. Astro 4:

---
const post = await getEntry('posts', slug);
const { Content } = await post.render();
---

Astro 5:

---
import { getEntry, render } from 'astro:content';
const post = await getEntry('posts', slug);
const { Content } = await render(post);
---

5. Update getStaticPaths

post.slug is now post.id in Astro 5:

// Astro 4
return posts.map((post) => ({ params: { slug: post.slug } }));

// Astro 5
return posts.map((post) => ({ params: { slug: post.id } }));

Yang harus diwaspadai

  • Image fields: Astro 5 mengubah cara image() Zod helper bekerja. Pastikan import dari astro:content masih works untuk schema dengan image field.
  • Reference fields: reference() di schema tetap bekerja, tetapi pastikan target collection juga sudah menggunakan loader API.
  • Plugins yang custom load content (e.g., dari API external) — sekarang lebih sustainable dengan custom loader. Lihat docs untuk defineLoader.

Bug yang saya temukan

Saat migrate, saya temukan satu bug yang tidak immediately obvious:

Issue: pubDate Date type tidak parse dengan benar setelah migrate. Frontmatter pubDate: 2026-04-22 (tanpa quotes) yang previously bekerja sekarang error.

Fix: wrap dengan z.coerce.date() instead of z.date():

schema: z.object({
  pubDate: z.coerce.date(),  // bukan z.date()
})

Astro 5 lebih strict dengan type handling di YAML frontmatter. z.coerce.date() lebih forgiving.

Performance

Tidak ada perubahan signifikan di build time setelah migrate. Project saya yang 200+ markdown files build dalam 8-12 detik di Astro 4, sekarang 9-13 detik di Astro 5. Difference within noise.

Tetapi developer experience lebih baik — error message saat schema validation gagal sekarang menunjuk file + line yang tepat. Di Astro 4 sering hanya “validation failed” tanpa context.

Kalau Anda belum siap migrate

Astro 4 LTS support sampai akhir 2025. Tetapi sebagian besar integration baru (@astrojs/mdx ^5, @astrojs/starlight ^0.39) hanya support Astro 5/6. Pinning Astro 4 berarti pinning integration versions juga.

Saran saya: migrate sebelum Anda butuh integration update. Lebih predictable daripada forced migration di tengah-tengah feature work.


Migration ini dilakukan pada 3 project Astro saya selama April 2026. Repo contoh dengan before/after di github.com/ricky/astro-5-migration-example — placeholder, ganti dengan repo nyata.