Web Development

Astro: Web Framework Modern untuk Content-Driven Website

Tutorial lengkap belajar Astro β€” Islands Architecture, SSG & SSR, content collections, integrations, routing, dan membangun website super cepat dengan zero JavaScript by default

1. Pengenalan Astro

Astro adalah web framework all-in-one yang dirancang untuk membangun website yang cepat, content-driven. Dibuat oleh Fred K. Schott dan tim, Astro mengambil pendekatan unik: zero JavaScript by default. Semua halaman di-render sebagai HTML murni di server, dan JavaScript hanya dikirim ke browser saat benar-benar dibutuhkan melalui konsep yang disebut Islands Architecture.

Ini menjadikan Astro sangat cocok untuk blog, dokumentasi, portofolio, situs perusahaan, landing page, dan e-commerce β€” di mana konten dan kecepatan adalah prioritas utama.

Mengapa Memilih Astro?

Keunggulan Penjelasan
Zero JS DefaultTidak ada JavaScript yang dikirim ke browser kecuali Anda secara eksplisit menambahkannya
Framework-AgnosticGunakan React, Vue, Svelte, Solid, Preact, atau vanilla JS dalam satu proyek
Islands ArchitectureInteraktif hanya di bagian yang perlu β€” sisanya HTML statis
Content CollectionsType-safe markdown/MDX content management built-in
Super CepatSkor Lighthouse 100/100 out of the box β€” karena HTML murni
SSG & SSRDukungan static site generation dan server-side rendering

Astro vs Framework Lain

Aspek Astro Next.js Nuxt.js SvelteKit
Primary UseContent websitesWeb appsWeb appsWeb apps
Default JSZero (HTML)React (client)Vue (client)Svelte (client)
Multi-frameworkβœ… Ya❌ React only❌ Vue only❌ Svelte only
Learning Curve🟒 Mudah🟑 Sedang🟑 Sedang🟒 Mudah
Best ForBlog, docs, portoComplex web appsComplex web appsFast web apps
Performance⚑ Sangat cepat🟑 Tergantung SSR🟑 Tergantung SSR⚑ Cepat
Diagram: Islands Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            ASTRO ISLANDS ARCHITECTURE                 β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚              HTML Page (statik)                  β”‚  β”‚
β”‚  β”‚                                                 β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  β”‚
β”‚  β”‚  β”‚  πŸ“„ Header β€” HTML murni, 0 KB JS         β”‚   β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β”‚
β”‚  β”‚                                                 β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚  β”‚
β”‚  β”‚  β”‚ πŸ“ Blog Post     β”‚  β”‚ 🌴 React Island  β”‚     β”‚  β”‚
β”‚  β”‚  β”‚ HTML statis      β”‚  β”‚ (client:idle)    β”‚     β”‚  β”‚
β”‚  β”‚  β”‚ 0 KB JavaScript  β”‚  β”‚ 42 KB JavaScript β”‚     β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”‚
β”‚  β”‚                                                 β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚  β”‚
β”‚  β”‚  β”‚ πŸ“„ Sidebar       β”‚  β”‚ πŸ”₯ Svelte Island β”‚     β”‚  β”‚
β”‚  β”‚  β”‚ HTML statis      β”‚  β”‚ (client:visible) β”‚     β”‚  β”‚
β”‚  β”‚  β”‚ 0 KB JavaScript  β”‚  β”‚ 2 KB JavaScript  β”‚     β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”‚
β”‚  β”‚                                                 β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  β”‚
β”‚  β”‚  β”‚  πŸ“„ Footer β€” HTML murni, 0 KB JS         β”‚   β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                       β”‚
β”‚  Total JS: 44 KB (bukan 200+ KB seperti SPA biasa)   β”‚
β”‚  Islands = komponen interaktif yang hydrated client    β”‚
β”‚  Sisanya = HTML statis yang sangat cepat              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Setup Proyek Astro

Astro dapat di-setup dengan sangat cepat. CLI wizard interaktif akan memandu Anda memilih template, TypeScript, dan integrasi framework.

Bash β€” Setup Astro
# Buat proyek Astro baru
npm create astro@latest belajar-astro

# CLI wizard akan menanyakan:
# βœ” Where should we create your new project? β†’ belajar-astro
# βœ” How would you like to start your new project? β†’ Include sample files
# βœ” Do you plan to write TypeScript? β†’ Yes (recommended)
# βœ” Install dependencies? β†’ Yes
# βœ” Initialize a new git repository? β†’ Yes

# Masuk ke direktori
cd belajar-astro

# Jalankan development server
npm run dev

# Output:
#   astro  v4.x
#   ┃ Local    http://localhost:4321/
#   ┃ Network  http://192.168.1.x:4321/

# Build untuk production
npm run build

# Preview hasil build
npm run preview

Struktur Proyek Astro

File Structure
belajar-astro/
β”œβ”€β”€ public/                    ← File statis (gambar, favicon, robots.txt)
β”‚   └── favicon.svg
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/            ← Komponen UI (Astro, React, Vue, Svelte)
β”‚   β”‚   β”œβ”€β”€ Header.astro
β”‚   β”‚   β”œβ”€β”€ Footer.astro
β”‚   β”‚   β”œβ”€β”€ Card.astro
β”‚   β”‚   β”œβ”€β”€ Counter.jsx        ← React island
β”‚   β”‚   └── Search.svelte      ← Svelte island
β”‚   β”œβ”€β”€ content/               ← Content collections
β”‚   β”‚   β”œβ”€β”€ blog/
β”‚   β”‚   β”‚   β”œβ”€β”€ post-1.md
β”‚   β”‚   β”‚   └── post-2.mdx
β”‚   β”‚   └── config.ts          ← Schema definisi
β”‚   β”œβ”€β”€ layouts/               ← Layout wrappers
β”‚   β”‚   β”œβ”€β”€ BaseLayout.astro
β”‚   β”‚   └── BlogLayout.astro
β”‚   β”œβ”€β”€ pages/                 ← File-based routing
β”‚   β”‚   β”œβ”€β”€ index.astro        β†’ /
β”‚   β”‚   β”œβ”€β”€ about.astro        β†’ /about
β”‚   β”‚   β”œβ”€β”€ blog/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.astro    β†’ /blog
β”‚   β”‚   β”‚   └── [slug].astro   β†’ /blog/:slug
β”‚   β”‚   └── api/
β”‚   β”‚       └── data.json.ts   β†’ /api/data (API endpoint)
β”‚   └── styles/
β”‚       └── global.css
β”œβ”€β”€ astro.config.mjs           ← Konfigurasi Astro
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
└── tailwind.config.mjs        ← (opsional) Tailwind config

Konfigurasi Astro

JavaScript β€” astro.config.mjs
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';

// https://astro.build/config
export default defineConfig({
  // Site URL (untuk sitemap & SEO)
  site: 'https://belajar-astro.pages.dev',

  // Integrations: framework & fitur yang ingin ditambahkan
  integrations: [
    react(),
    svelte(),
    tailwind(),
    mdx(),
    sitemap(),
  ],

  // Output mode
  // 'static' (default) β€” SSG, semua halaman jadi HTML statis
  // 'server' β€” SSR, render di server setiap request
  // 'hybrid' β€” campuran (sebagian statis, sebagian SSR)
  output: 'static',

  // Vite config (Astro menggunakan Vite di balik layar)
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `$accent: #6366f1;`
        }
      }
    }
  },

  // Markdown config
  markdown: {
    shikiConfig: {
      theme: 'catppuccin-mocha',
      wrap: true,
    },
  },
});
πŸ’‘ Multi-Framework

Salah satu keunggulan terbesar Astro adalah dukungan multi-framework. Anda bisa menggunakan komponen React, Vue, Svelte, Solid, Preact, dan Lit dalam satu proyek yang sama. Cukup tambahkan integrasinya di astro.config.mjs.

3. Komponen Astro (.astro)

File .astro adalah format komponen unik Astro. Ia menggunakan sintaks yang mirip dengan JSX tetapi memiliki beberapa perbedaan penting: frontmatter script di bagian atas (di antara fence ---) dan template expressions di bagian bawah.

Anatomi Komponen Astro

Astro β€” Component Anatomy
---
// ====== FRONTMATTER SCRIPT ======
// Kode JavaScript dieksekusi di BUID TIME (server-side)
// Tidak pernah dikirim ke browser!

// Import komponen
import Button from '../components/Button.astro';
import Card from '../components/Card.astro';

// Akses props
interface Props {
  judul: string;
  deskripsi?: string;
  warna?: string;
}

const { judul, deskripsi = 'Tidak ada deskripsi', warna = '#6366f1' } = Astro.props;

// Fetch data (build-time)
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Logika JavaScript
const daftarItem = ['Roti', 'Susu', 'Telur', 'Kopi'];
const tanggal = new Date().toLocaleDateString('id-ID', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});

// Helper functions
function formatRupiah(angka: number): string {
  return new Intl.NumberFormat('id-ID', {
    style: 'currency',
    currency: 'IDR',
    minimumFractionDigits: 0,
  }).format(angka);
}
---


{judul}

{tanggal}

{deskripsi}

2 + 2 = {2 + 2}

Harga: {formatRupiah(150000)}

{data.length > 0 ? (
    {data.map((item: any) => (
  • {item.nama}
  • ))}
) : (

Tidak ada data

)}
    {daftarItem.map(item =>
  • {item}
  • )}

Layout Components

Astro β€” Layouts
---
// ====== src/layouts/BaseLayout.astro ======
interface Props {
  judul: string;
  deskripsi?: string;
}

const { judul, deskripsi = 'Website tutorial BeebaneLabs' } = Astro.props;
---

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{judul} | BeebaneLabs</title>
  <meta name="description" content={deskripsi}>
  <link rel="stylesheet" href="/styles/global.css">
</head>
<body>
  <header>
    <nav>
      <a href="/">Beranda</a>
      <a href="/blog">Blog</a>
      <a href="/about">Tentang</a>
    </nav>
  </header>

  <main>
    <slot /> <!-- Konten halaman di-render di sini -->
  </main>

  <footer>
    <p>&copy; 2026 BeebaneLabs</p>
  </footer>
</body>
</html>

// ====== src/layouts/BlogLayout.astro ======
---
import BaseLayout from './BaseLayout.astro';

interface Props {
  judul: string;
  deskripsi: string;
  tanggal: string;
  gambar?: string;
  tags?: string[];
}

const { judul, deskripsi, tanggal, gambar, tags = [] } = Astro.props;
---

<BaseLayout judul={judul} deskripsi={deskripsi}>
  <article class="blog-post">
    {gambar && <img src={gambar} alt={judul} class="hero-image" />}
    <h1>{judul}</h1>
    <time>{tanggal}</time>
    {tags.length > 0 && (
      <div class="tags">
        {tags.map(tag => <span class="tag">{tag}</span>)}
      </div>
    )}
    <div class="content">
      <slot />
    </div>
  </article>
</BaseLayout>

// ====== Menggunakan layout di halaman ======
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout judul="Tentang Kami">
  <h1>Tentang BeebaneLabs</h1>
  <p>Kami adalah portal tutorial teknologi Indonesia.</p>
</BaseLayout>

4. Islands Architecture

Islands Architecture adalah konsep inti Astro. Di halaman yang sebagian besar statis, komponen interaktif dianggap sebagai "pulau" (island) yang hidup di lautan HTML statis. Setiap island bisa hydrated secara independen dengan strategi yang berbeda.

Client Directives

Astro β€” Client Directives
---
// Import komponen dari framework manapun
import ReactCounter from '../components/Counter.jsx';
import VueGallery from '../components/Gallery.vue';
import SvelteSearch from '../components/Search.svelte';
import SolidSidebar from '../components/Sidebar.tsx';
import LitButton from '../components/my-button.js';
---

<!-- ====== 1. client:idle β€” hydrated saat browser idle ====== -->
<!-- Cocok untuk: komponen non-urgent yang tidak perlu langsung interaktif -->
<ReactCounter client:idle />

<!-- ====== 2. client:visible β€” hydrated saat terlihat di viewport ====== -->
<!-- Cocok untuk: komponen di bawah fold, lazy loading -->
<VueGallery client:visible />

<!-- ====== 3. client:load β€” hydrated segera saat halaman dimuat ====== -->
<!-- Cocok untuk: komponen yang harus langsung interaktif -->
<SvelteSearch client:load />

<!-- ====== 4. client:media β€” hydrated berdasarkan media query ====== -->
<!-- Cocok untuk: komponen responsif yang berbeda per ukuran layar -->
<SolidSidebar client:media="(max-width: 768px)" />

<!-- ====== 5. client:only β€” skip rendering server, render di client saja ====== -->
<!-- Cocok untuk: komponen yang hanya bisa di-render di browser -->
<LitButton client:only="lit" />

<!-- Komponen TANPA client directive = HTML statis, 0 JS! -->
<ReactCounter /> <!-- Hanya HTML, tidak interaktif di client -->
Diagram: Hydration Strategies
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          CLIENT HYDRATION STRATEGIES                  β”‚
β”‚                                                       β”‚
β”‚  client:load ──────→ πŸ’§ Hydrate segera saat load      β”‚
β”‚  β”‚                    Urgent, interaktif langsung      β”‚
β”‚  β”‚                    Contoh: search bar, form login   β”‚
β”‚  β”‚                                                    β”‚
β”‚  client:idle ──────→ πŸ’€ Hydrate saat browser idle      β”‚
β”‚  β”‚                    Tidak urgent, bisa tunggu        β”‚
β”‚  β”‚                    Contoh: footer chat, widget      β”‚
β”‚  β”‚                                                    β”‚
β”‚  client:visible ──→ πŸ‘οΈ Hydrate saat visible di viewportβ”‚
β”‚  β”‚                    Lazy load, hemat bandwidth       β”‚
β”‚  β”‚                    Contoh: gallery, komentar        β”‚
β”‚  β”‚                                                    β”‚
β”‚  client:media ────→ πŸ“± Hydrate berdasarkan media query β”‚
β”‚  β”‚                    Responsive interactivity         β”‚
β”‚  β”‚                    Contoh: mobile menu, sidebar     β”‚
β”‚  β”‚                                                    β”‚
β”‚  client:only ─────→ πŸ–₯️ Render & hydrate di client     β”‚
β”‚                      Skip SSR sepenuhnya              β”‚
β”‚                      Contoh: WebGL, browser-specific  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. Routing & Pages

Astro menggunakan file-based routing. Setiap file di src/pages/ secara otomatis menjadi rute. Astro mendukung static routes, dynamic routes, dan bahkan API endpoints.

Astro β€” Routing
// ====== Static Routes ======
// src/pages/index.astro           β†’ /
// src/pages/about.astro           β†’ /about
// src/pages/blog/index.astro      β†’ /blog
// src/pages/blog/archive.astro    β†’ /blog/archive

// ====== Dynamic Routes ======
// src/pages/blog/[slug].astro     β†’ /blog/:slug
// src/pages/produk/[id].astro     β†’ /produk/:id
// src/pages/[...path].astro       β†’ /* (catch-all)

// ====== src/pages/blog/[slug].astro ======
---
import BlogLayout from '../../layouts/BlogLayout.astro';

// getStaticPaths WAJIB untuk static output
// Mendefinisikan semua kemungkinan nilai slug
export async function getStaticPaths() {
  const posts = await fetch('https://api.example.com/posts')
    .then(r => r.json());

  return posts.map((post: any) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

// Terima props dari getStaticPaths
const { post } = Astro.props;
---

<BlogLayout
  judul={post.title}
  deskripsi={post.excerpt}
  tanggal={post.date}
  tags={post.tags}
>
  <div set:html={post.content} />
</BlogLayout>

// ====== src/pages/produk/[...slug].astro (Catch-all) ======
---
// [...slug] bisa menangkap /produk/a, /produk/a/b, /produk/a/b/c
export async function getStaticPaths() {
  return [
    { params: { slug: 'elektronik/laptop' } },
    { params: { slug: 'elektronik/hp' } },
    { params: { slug: 'pakaian/kaos' } },
  ];
}

const { slug } = Astro.params;
const parts = slug?.split('/') || [];
---

<h1>Breadcrumb: {parts.join(' > ')}</h1>

// ====== src/pages/api/data.json.ts (API Endpoint) ======
---
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ request }) => {
  const url = new URL(request.url);
  const limit = Number(url.searchParams.get('limit') || 10);

  const data = [
    { id: 1, nama: 'Produk A', harga: 150000 },
    { id: 2, nama: 'Produk B', harga: 250000 },
  ].slice(0, limit);

  return new Response(JSON.stringify(data), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const body = await request.json();
  return new Response(JSON.stringify({ success: true, data: body }), {
    status: 201,
  });
};

6. Content Collections

Content Collections adalah fitur Astro untuk mengelola konten Markdown, MDX, dan data. Collections memberikan type-safe frontmatter validation dan API query yang powerful.

Astro β€” Content Collections
// ====== src/content/config.ts β€” Definisikan schema ======
import { defineCollection, z } from 'astro:content';

// Collection: blog posts
const blogCollection = defineCollection({
  type: 'content', // 'content' untuk .md/.mdx, 'data' untuk .json/.yaml
  schema: z.object({
    judul: z.string(),
    deskripsi: z.string(),
    tanggal: z.string().transform(str => new Date(str)),
    gambar: z.string().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    kategori: z.enum(['tutorial', 'artikel', 'berita']).default('artikel'),
    penulis: z.string().default('Admin'),
  }),
});

// Collection: produk
const produkCollection = defineCollection({
  type: 'data',
  schema: z.object({
    nama: z.string(),
    harga: z.number().positive(),
    kategori: z.string(),
    stok: z.number().int().min(0),
    gambar: z.string().url(),
  }),
});

// Export collections
export const collections = {
  blog: blogCollection,
  produk: produkCollection,
};

// ====== File: src/content/blog/membuat-website-astro.md ======
// ---
// judul: "Membuat Website dengan Astro"
// deskripsi: "Panduan lengkap membuat website cepat dengan Astro framework"
// tanggal: "2026-06-26"
// tags: ["astro", "web", "tutorial"]
// kategori: "tutorial"
// penulis: "Budi Santoso"
// ---
//
// ## Pengenalan
//
// Astro adalah framework yang...
//
// ## Setup
//
// Jalankan perintah berikut...

// ====== Menggunakan collections di halaman ======
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';

// Ambil semua post yang bukan draft
const posts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => b.data.tanggal.getTime() - a.data.tanggal.getTime());
---

<BaseLayout judul="Blog">
  <h1>Blog</h1>
  <div class="posts-grid">
    {posts.map(post => (
      <article class="post-card">
        {post.data.gambar && <img src={post.data.gambar} alt={post.data.judul} />}
        <h2><a href={`/blog/${post.slug}`}>{post.data.judul}</a></h2>
        <p>{post.data.deskripsi}</p>
        <div class="meta">
          <time>{post.data.tanggal.toLocaleDateString('id-ID')}</time>
          <span>oleh {post.data.penulis}</span>
        </div>
        <div class="tags">
          {post.data.tags.map(tag => <span class="tag">#{tag}</span>)}
        </div>
      </article>
    ))}
  </div>
</BaseLayout>

// ====== src/pages/blog/[slug].astro β€” Detail post ======
---
import { getCollection, render } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';

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);
---

<BlogLayout judul={post.data.judul} deskripsi={post.data.deskripsi} tanggal={post.data.tanggal.toLocaleDateString('id-ID')}>
  <Content />
</BlogLayout>

7. Data Fetching & API

Di Astro, data fetching terjadi di frontmatter script (bagian ---) yang dieksekusi saat build time (untuk SSG) atau setiap request (untuk SSR). Astro mendukung fetch API native dan bisa mengakses database, API, atau file lokal.

Astro β€” Data Fetching
// ====== Fetch dari External API ======
---
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
const posts = await response.json();
---

{posts.map(post => (
  <article>
    <h2>{post.title}</h2>
    <p>{post.body}</p>
  </article>
))}

// ====== Fetch dengan Error Handling ======
---
let data = [];
let error = null;

try {
  const response = await fetch('https://api.example.com/products');
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  data = await response.json();
} catch (err) {
  error = err.message;
  console.error('Gagal fetch data:', err);
}
---

{error ? (
  <div class="error">Gagal memuat data: {error}</div>
) : (
  <div class="grid">
    {data.map(item => <Card {...item} />)}
  </div>
)}

// ====== Fetch dengan Environment Variables ======
---
// Pastikan env variable tersedia di .env
// ASTRO_API_KEY=your_secret_key
// PUBLIC_API_URL=https://api.example.com

const apiKey = import.meta.env.ASTRO_API_KEY; // Server-only (tidak exposed ke client)
const apiUrl = import.meta.env.PUBLIC_API_URL; // Public (exposed ke client)

const response = await fetch(`${apiUrl}/data`, {
  headers: { 'Authorization': `Bearer ${apiKey}` }
});
const data = await response.json();
---

// ====== Fetch dari Local Files ======
---
import fs from 'node:fs';
import path from 'node:path';

// Baca file JSON lokal
const configPath = path.resolve('./src/data/config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));

// Atau gunakan import
import teamData from '../data/team.json';
---

// ====== SSR: Fetch dari database ======
---
// Pastikan output: 'server' di astro.config.mjs
import { db } from '../lib/database';

// Kode ini berjalan di server setiap request (SSR)
const user = await Astro.locals.user;
const orders = await db.orders.findMany({
  where: { userId: user.id },
  orderBy: { createdAt: 'desc' },
});
---

8. Integrations & Adapters

Astro memiliki ekosistem integrasi yang kaya. Integrations menambahkan fitur seperti framework UI, CSS tools, dan optimizer. Adapters memungkinkan deployment ke berbagai hosting platform.

Bash β€” Menambahkan Integrations
# Tambahkan integrasi menggunakan CLI Astro
npx astro add react          # React components
npx astro add vue            # Vue components
npx astro add svelte         # Svelte components
npx astro add tailwind       # Tailwind CSS
npx astro add mdx            # MDX support
npx astro add sitemap        # Auto sitemap
npx astro add partytown      # Third-party script optimization
npx astro add prefetch       # Link prefetching

# Tambahkan adapter untuk deployment
npx astro add node           # Node.js server
npx astro add vercel         # Vercel deployment
npx astro add netlify        # Netlify deployment
npx astro add cloudflare     # Cloudflare Pages
npx astro add deno           # Deno Deploy

# Contoh konfigurasi dengan beberapa integrations
# astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  integrations: [react(), tailwind(), mdx(), sitemap()],
  output: 'server',
  adapter: vercel(),
});

Contoh Integrasi Lengkap

Astro β€” Full Integration Example
// ====== Menggunakan React + Svelte + Tailwind dalam satu proyek ======

// src/components/ReactCounter.jsx (React Island)
import { useState } from 'react';

export default function ReactCounter() {
  const [count, setCount] = useState(0);
  return (
    <div className="p-4 bg-slate-800 rounded-lg">
      <p className="text-xl text-white">React Counter: {count}</p>
      <button onClick={() => setCount(c => c + 1)} className="btn-primary">
        Tambah
      </button>
    </div>
  );
}

// src/components/SvelteSearch.svelte (Svelte Island)
<script>
  let query = '';
  $: results = query.length > 2 ? ['Hasil 1', 'Hasil 2'] : [];
</script>

<input bind:value={query} placeholder="Cari..." />
{#each results as result}
  <p>{result}</p>
{/each}

// src/pages/index.astro β€” Gabungan keduanya!
---
import BaseLayout from '../layouts/BaseLayout.astro';
import ReactCounter from '../components/ReactCounter.jsx';
import SvelteSearch from '../components/SvelteSearch.svelte';
---

<BaseLayout judul="Beranda">
  <h1 class="text-4xl font-bold text-white">Selamat Datang</h1>

  <!-- React island: hydrated saat visible -->
  <ReactCounter client:visible />

  <!-- Svelte island: hydrated saat load -->
  <SvelteSearch client:load />

  <!-- Semua di luar islands = HTML statis, 0 JS! -->
</BaseLayout>

9. Deployment

Astro bisa di-deploy ke hampir semua platform hosting. Untuk SSG (static), cukup upload folder dist/. Untuk SSR, perlu adapter yang sesuai.

Bash β€” Deployment
# Build untuk production
npm run build

# Folder output: dist/
# β”œβ”€β”€ _astro/          ← CSS & JS assets
# β”œβ”€β”€ index.html       ← Halaman utama
# β”œβ”€β”€ about.html       ← Halaman about
# β”œβ”€β”€ blog/            ← Halaman blog
# └── ...

# ====== Deploy ke Cloudflare Pages ======
# 1. Push ke GitHub
# 2. Connect repo di Cloudflare Dashboard
# 3. Settings:
#    Build command: npm run build
#    Build output directory: dist
# 4. Tambahkan adapter: npm i @astrojs/cloudflare
# 5. Update astro.config.mjs:
#    output: 'server',
#    adapter: cloudflare(),

# ====== Deploy ke Vercel ======
npx vercel

# ====== Deploy ke Netlify ======
# Netlify auto-detect Astro project
# Atau manual: drag & drop folder dist/ ke Netlify dashboard

# ====== Deploy ke GitHub Pages ======
# 1. Tambahkan site URL di astro.config.mjs
# 2. Jalankan: npm run build
# 3. Push folder dist/ ke branch gh-pages

# ====== Preview build secara lokal ======
npm run preview
# β†’ http://localhost:4321/

10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Astro:

Pertanyaan 1: Apa konsep utama Astro yang membuat website sangat cepat?

a) Virtual DOM yang dioptimalkan
b) Zero JavaScript by default β€” semua halaman jadi HTML murni
c) Menggunakan WebAssembly untuk semua rendering
d) Service Worker yang meng-cache semua halaman

Pertanyaan 2: Apa fungsi dari client:visible directive?

a) Menyembunyikan komponen dari tampilan
b) Menghidrasi komponen hanya saat browser idle
c) Menghidrasi komponen saat terlihat di viewport
d) Membuat komponen selalu visible

Pertanyaan 3: Bisakah Anda menggunakan React dan Vue dalam satu proyek Astro?

a) Tidak, hanya boleh satu framework
b) Ya, Astro mendukung multi-framework dalam satu proyek
c) Hanya React dan Svelte yang bisa digabung
d) Hanya Vue dan Svelte yang bisa digabung

Pertanyaan 4: Apa yang dimaksud dengan Content Collections di Astro?

a) Kumpulan gambar dan asset multimedia
b) Sistem untuk mengelola konten Markdown/MDX dengan type-safe schema
c) Database bawaan Astro untuk menyimpan data
d) Plugin untuk CMS headless

Pertanyaan 5: Apa perbedaan output mode 'static' dan 'server' di Astro?

a) 'static' = semua halaman HTML murni di build, 'server' = render di server setiap request
b) 'static' lebih lambat dari 'server'
c) 'server' tidak mendukung dynamic routes
d) Tidak ada perbedaan, hanya nama berbeda
πŸ” Zoom
100%
🎨 Tema