Sebuah halaman blog idealnya cuma butuh HTML dan CSS. Tapi kebanyakan framework modern tetap mengirim puluhan kilobyte JavaScript ke browser hanya untuk merender satu paragraf. Astro mengambil jalan berbeda: nol JavaScript secara default. JS cuma dimuat kalau komponennya benar-benar interaktif. Hasilnya, blog yang kamu bangun pakai Astro biasanya lolos Lighthouse dengan skor mendekati 100 tanpa banyak utak-atik. Saya pakai Astro untuk beberapa proyek sampingan, dan ini framework pertama yang bikin saya berhenti mikirin ukuran bundle.

Kenapa Astro Beda dari Next.js dan Framework Lain

Kuncinya ada di islands architecture. Seluruh halaman dirender jadi HTML statis saat build, lalu bagian yang butuh interaktivitas — tombol like, form pencarian, kolom komentar — diperlakukan sebagai pulau terpisah. Pulau itu baru mengunduh JavaScript saat dibutuhkan. Bandingkan dengan Belajar Next.js untuk Pemula, di mana React di-hydrate untuk seluruh halaman. Astro membalik pendekatan itu: yang statis ya tetap statis.

Install dan Struktur Proyek

Buka terminal, lalu jalankan perintah berikut. Template minimal memberi kamu proyek bersih tanpa contoh yang harus dihapus dulu.

bash
npm create astro@latest blogku -- --template minimal --no-install --no-git
cd blogku
npm install
npm run dev

Buka http://localhost:4321. Kamu bakal lihat halaman sederhana dengan teks “Astro”. Struktur foldernya begini:

bash
blogku/
├── src/
│   ├── pages/          # rute berbasis file
│   │   └── index.astro
│   ├── content/        # tempat markdown artikel
│   └── components/     # komponen yang bisa dipakai ulang
├── astro.config.mjs
└── package.json

Mirip framework lain yang memakai file-based routing: file src/pages/index.astro jadi halaman /, dan src/pages/about.astro jadi /about. Tidak perlu konfigurasi router.

Content Collections: Artikel sebagai Markdown

Untuk blog, menulis artikel sebagai file Markdown jauh lebih nyaman daripada menulis komponen. Astro punya content collections untuk ini. Pertama, definisikan schema koleksi di src/content.config.ts. Schema-nya memakai Zod — sama seperti yang dibahas di tutorial Zod — supaya setiap artikel punya frontmatter yang konsisten.

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

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

export const collections = { blog };

Lalu buat artikel pertama di src/content/blog/artikel-pertama.md. Frontmatter yang tidak sesuai schema bakal bikin build gagal — itu justru bagus, karena typo ketahuan sebelum artikel tayang.

other
---
title: "Kenapa Saya Pindah ke Astro"
description: "Alasan pindah dari framework lama"
pubDate: 2026-10-20
tags: ["astro", "web"]
---

Ini isi artikel. Markdown biasa, tanpa perlu tahu HTML.

Menampilkan Daftar Artikel

Sekarang ganti src/pages/index.astro agar menampilkan daftar artikel. Perhatikan bagian antara ---: itulah frontmatter Astro, tempat kode JavaScript/TypeScript dijalankan saat build.

html
---
import { getCollection } from 'astro:content';

const posts = (await getCollection('blog')).sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Blogku</title>
  </head>
  <body>
    <main>
      <h1>Semua Tulisan</h1>
      <ul>
        {posts.map((post) => (
          <li>
            <a href={`/blog/${post.slug}/`}>
              <h2>{post.data.title}</h2>
            </a>
            <time datetime={post.data.pubDate.toISOString()}>
              {post.data.pubDate.toLocaleDateString('id-ID')}
            </time>
          </li>
        ))}
      </ul>
    </main>
  </body>
</html>

Halaman Detail Artikel dengan Rute Dinamis

Buat src/pages/blog/[slug].astro. Fungsi getStaticPaths() memberitahu Astro semua rute statis yang harus digenerate saat build.

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

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

const { post } = Astro.props;
const { Content } = await render(post);
---
<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>

Konten markdown baru langsung muncul di dev server tanpa restart. Tapi kalau kamu mengubah schema di src/content.config.ts, restart npm run dev dulu agar Astro membaca ulang definisinya.

Islands Architecture: Komponen Interaktif Tanpa Memuat JS di Semua Halaman

Sekarang buktikan konsep islands. Install React ke dalam proyek:

bash
npx astro add react

Perintah ini menambah dependensi React dan mengubah astro.config.mjs secara otomatis. Buat komponen interaktif sederhana:

javascript
import { useState } from 'react';

export default function LikeButton({ initial = 0 }) {
  const [likes, setLikes] = useState(initial);
  return (
    <button onClick={() => setLikes(likes + 1)}>
      Suka ({likes})
    </button>
  );
}

Pasang di index.astro dengan directive client:load:

html
---
import LikeButton from '../components/LikeButton.jsx';
import { getCollection } from 'astro:content';

const posts = (await getCollection('blog')).sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Blogku</title>
  </head>
  <body>
    <main>
      <h1>Semua Tulisan</h1>
      <LikeButton initial={3} client:load />
      <ul>
        {posts.map((post) => (
          <li>
            <a href={`/blog/${post.slug}/`}>{post.data.title}</a>
          </li>
        ))}
      </ul>
    </main>
  </body>
</html>

Tanpa directive client:*, komponen React dirender jadi HTML statis dan tidak interaktif — klik tombolnya tidak melakukan apa-apa. Itu default yang disengaja. Directive client:load meminta React di-hydrate segera; client:visible menunggu komponen masuk viewport; client:idle menunggu browser senggang. Untuk tombol like yang berada di bawah lipatan halaman, client:visible menghemat banyak data awal.

Lihat hasilnya di DevTools tab Network: halaman beranda hanya memuat JavaScript untuk LikeButton, bukan seluruh bundle React untuk semua halaman. Inilah bedanya dengan SPA.

Styling dengan Tailwind (Opsional)

Astro tidak memaksa framework CSS apa pun. Kalau mau cepat, pasang Tailwind — prosesnya sudah dibahas tuntas di Belajar Tailwind CSS. Satu perintah, lalu tinggal pakai class utility di komponen .astro seperti biasa.

Build dan Deploy

Jalankan npm run build dan Astro menghasilkan folder dist/ berisi HTML statis murni. Kamu bisa host di mana saja: Nginx, GitHub Pages, Cloudflare Pages, Vercel, atau Netlify. Tidak ada server Node yang harus dijalankan di produksi.

bash
npm run build
ls dist/
# index.html  blog/  ...

npm run preview   # lihat hasil build di localhost:4321

Dari sini kamu bisa menambah halaman /about, tag untuk mengelompokkan artikel, atau RSS feed. Coba satu hal dulu: pindahkan salah satu halaman statis yang kamu punya ke Astro, lalu ukur skor Lighthouse sebelum dan sesudahnya. Perbedaannya biasanya langsung terlihat di kolom Performance.